Skip to content

Commit

Permalink
Bluetooth: Controller: Integrate Zephyr PM calls in MPSL
Browse files Browse the repository at this point in the history
This approach enables us to call appropriate Power Management policies
from zephyr Power Management.

Within MPSL a global struct is defined and modified from high priority
context whenever an event is scheduled/rescheduled/...

Within nrf-sdk this global struct is read and appropriate zephyr
functions are called.

Note: If High Prio changed the parameters already before we could
process them, do nothing, we will get back here with next workqueue
item.

Unit Tests are added accordingly.

Signed-off-by: Kyra Lengfeld <[email protected]>
  • Loading branch information
KyraLengfeld authored and nordicjm committed May 14, 2024
1 parent 65dfa34 commit 2abf723
Show file tree
Hide file tree
Showing 13 changed files with 594 additions and 10 deletions.
5 changes: 3 additions & 2 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
/scripts/quarantine.yaml @nrfconnect/ncs-test-leads

# VS Code Configuration files
/.vscode/ @trond-snekvik
/.vscode/ @FilipZajdel

# Applications
/applications/asset_tracker_v2/ @nrfconnect/ncs-cia @coderbyheart
Expand Down Expand Up @@ -125,7 +125,7 @@ Kconfig* @tejlmand
/lib/modem_key_mgmt/ @rlubos
/lib/multithreading_lock/ @nrfconnect/ncs-dragoon
/lib/pdn/ @lemrey @rlubos
/lib/ram_pwrdn/ @mariuszpos @MarekPorwisz
/lib/ram_pwrdn/ @MarekPorwisz
/lib/fatal_error/ @KAGA164 @nordic-krch
/lib/sfloat/ @kapi-no @maje-emb
/lib/sms/ @trantanen @tokangas
Expand Down Expand Up @@ -334,6 +334,7 @@ Kconfig* @tejlmand
/tests/subsys/event_manager_proxy/ @rakons
/tests/subsys/app_event_manager/ @pdunaj @MarekPieta @rakons
/tests/subsys/fw_info/ @oyvindronningstad
/tests/subsys/mpsl/ @nrfconnect/ncs-dragoon
/tests/subsys/net/lib/aws_*/ @nrfconnect/ncs-cia
/tests/subsys/net/lib/azure_iot_hub/ @nrfconnect/ncs-cia
/tests/subsys/net/lib/fota_download/ @hakonfam @sigvartmh
Expand Down
33 changes: 33 additions & 0 deletions include/mpsl/mpsl_pm_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#ifndef MPSL_PM_UTILS_H__
#define MPSL_PM_UTILS_H__

#ifdef __cplusplus
extern "C" {
#endif

/** @brief Initialize MPSL Power Management
*
* This routine initializes MPSL PM (via `mpsl_pm_init`).
*/
void mpsl_pm_utils_init(void);

/** @brief Handles MPSL Power Management work
*
* This calls Zephyr Power Management policy functions according
* to MPSL PM requirements.
*
* @pre mpsl_pm_utils_init() needs to have been called before.
*/
void mpsl_pm_utils_work_handler(void);

#ifdef __cplusplus
}
#endif

#endif /* MPSL_PM_UTILS_H__ */
31 changes: 28 additions & 3 deletions include/mpsl/mpsl_work.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,37 @@ extern struct k_work_q mpsl_work_q;
* @note Can be called by ISRs.
*
* @param work Address of work item.
*/
static inline void mpsl_work_submit(struct k_work *work)
{
if (k_work_submit_to_queue(&mpsl_work_q, work) < 0) {
__ASSERT(false, "k_work_submit_to_queue() failed.");
}
}

/** @brief Submit an idle 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 This is a no-op if the work item is already scheduled or submitted,
* even if @p delay is @c K_NO_WAIT.
*
* @param dwork Address of delayable work item.
*
* @return N/A
* @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 int mpsl_work_submit(struct k_work *work)
static inline void mpsl_work_schedule(struct k_work_delayable *dwork, k_timeout_t delay)
{
return k_work_submit_to_queue(&mpsl_work_q, work);
if (k_work_schedule_for_queue(&mpsl_work_q, dwork, delay) < 0) {
__ASSERT(false, "k_work_schedule_for_queue() failed.");
}
}

#ifdef __cplusplus
Expand Down
4 changes: 4 additions & 0 deletions subsys/mpsl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ if (CONFIG_MPSL_CX AND NOT CONFIG_MPSL_FEM_ONLY)
add_subdirectory(cx)
endif()

if (CONFIG_MPSL_USE_ZEPHYR_PM)
add_subdirectory(pm)
endif()

add_subdirectory_ifdef(CONFIG_MPSL_PIN_DEBUG pin_debug)
1 change: 1 addition & 0 deletions subsys/mpsl/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ if !MPSL_FEM_ONLY
rsource "cx/Kconfig"
rsource "init/Kconfig"
rsource "pin_debug/Kconfig"
rsource "pm/Kconfig"

endif # !MPSL_FEM_ONLY

Expand Down
24 changes: 19 additions & 5 deletions subsys/mpsl/init/mpsl_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
#include <hal/nrf_ipc.h>
#endif

#if IS_ENABLED(CONFIG_MPSL_USE_ZEPHYR_PM)
#include <pm/mpsl_pm_utils.h>
#endif

LOG_MODULE_REGISTER(mpsl_init, CONFIG_MPSL_LOG_LEVEL);

/* The following two constants are used in nrfx_glue.h for marking these PPI
Expand Down Expand Up @@ -165,7 +169,7 @@ static uint8_t __aligned(4) timeslot_context[TIMESLOT_MEM_SIZE];

static void mpsl_low_prio_irq_handler(const void *arg)
{
k_work_submit_to_queue(&mpsl_work_q, &mpsl_low_prio_work);
mpsl_work_submit(&mpsl_low_prio_work);
}

static void mpsl_low_prio_work_handler(struct k_work *item)
Expand All @@ -176,7 +180,13 @@ static void mpsl_low_prio_work_handler(struct k_work *item)

errcode = MULTITHREADING_LOCK_ACQUIRE();
__ASSERT_NO_MSG(errcode == 0);

mpsl_low_priority_process();

#if IS_ENABLED(CONFIG_MPSL_USE_ZEPHYR_PM)
mpsl_pm_utils_work_handler();
#endif

MULTITHREADING_LOCK_RELEASE();
}

Expand Down Expand Up @@ -329,8 +339,8 @@ static void mpsl_calibration_work_handler(struct k_work *work)

mpsl_calibration_timer_handle();

k_work_schedule_for_queue(&mpsl_work_q, &calibration_work,
K_MSEC(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD));
mpsl_work_schedule(&calibration_work,
K_MSEC(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD));
}
#endif /* CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC && !CONFIG_SOC_SERIES_NRF54HX */

Expand Down Expand Up @@ -406,6 +416,10 @@ 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();
Expand Down Expand Up @@ -449,8 +463,8 @@ static int mpsl_low_prio_init(void)
mpsl_low_prio_irq_handler, NULL, 0);

#if defined(CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC) && !defined(CONFIG_SOC_SERIES_NRF54HX)
k_work_schedule_for_queue(&mpsl_work_q, &calibration_work,
K_MSEC(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD));
mpsl_work_schedule(&calibration_work,
K_MSEC(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD));
#endif /* CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC && !CONFIG_SOC_SERIES_NRF54HX */

return 0;
Expand Down
9 changes: 9 additions & 0 deletions subsys/mpsl/pm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

zephyr_library()

zephyr_library_sources(mpsl_pm_utils.c)
13 changes: 13 additions & 0 deletions subsys/mpsl/pm/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

config MPSL_USE_ZEPHYR_PM
bool "Use Zephyr's Power Management API"
depends on SOC_SERIES_NRF54HX
depends on MPSL
depends on PM
help
This option configures MPSL to use Zephyr's Power Management.
122 changes: 122 additions & 0 deletions subsys/mpsl/pm/mpsl_pm_utils.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#include <zephyr/kernel.h>
#include <mpsl_pm.h>
#include <mpsl_pm_config.h>
#include <zephyr/pm/policy.h>
#include <zephyr/logging/log.h>

#include <mpsl/mpsl_work.h>
#include <mpsl/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 MAX_DELAY_SINCE_READING_PARAMS_US 50
#define TIME_TO_REGISTER_EVENT_IN_ZEPHYR_US 1000
#define PM_MAX_LATENCY_HCI_COMMANDS_US 4999999

static void m_work_handler(struct k_work *work);
static K_WORK_DELAYABLE_DEFINE(pm_work, m_work_handler);

#define RETRY_TIME_MAX_US (UINT32_MAX - TIME_TO_REGISTER_EVENT_IN_ZEPHYR_US)

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 void m_update_latency_request(uint32_t lat_value_us)
{
if (m_prev_lat_value_us != lat_value_us) {
pm_policy_latency_request_update(&m_latency_req, lat_value_us);
m_prev_lat_value_us = lat_value_us;
}
}

void mpsl_pm_utils_work_handler(void)
{
mpsl_pm_params_t params = {0};
bool pm_param_valid = mpsl_pm_params_get(&params);

if (m_pm_prev_flag_value == params.cnt_flag) {
/* We have no new info to process.*/
return;
}
if (!pm_param_valid) {
/* High prio did change mpsl_pm_params, while we read the params. */
m_pm_prev_flag_value = params.cnt_flag;
return;
}
switch (params.event_state) {
case MPSL_PM_EVENT_STATE_NO_EVENTS_LEFT:
{
/* No event scheduled, so set latency to restrict deepest sleep states*/
m_update_latency_request(PM_MAX_LATENCY_HCI_COMMANDS_US);
if (m_pm_event_is_registered) {
pm_policy_event_unregister(&m_evt);
m_pm_event_is_registered = false;
}
break;
}
case MPSL_PM_EVENT_STATE_BEFORE_EVENT:
{
/* Event scheduled */
uint64_t event_time_us = params.event_time_rel_us -
MAX_DELAY_SINCE_READING_PARAMS_US;

/* In case we missed a state and are in zero-latency, set low-latency.*/
m_update_latency_request(PM_MAX_LATENCY_HCI_COMMANDS_US);

if (event_time_us > UINT32_MAX) {
mpsl_work_schedule(&pm_work, K_USEC(RETRY_TIME_MAX_US));
return;
}

if (m_pm_event_is_registered) {
pm_policy_event_update(&m_evt, event_time_us);
} else {
pm_policy_event_register(&m_evt, event_time_us);
m_pm_event_is_registered = true;
}
break;
}
case MPSL_PM_EVENT_STATE_IN_EVENT:
{
m_update_latency_request(0);
break;
}
default:
__ASSERT(false, "MPSL PM is in an undefined state.");
}
m_pm_prev_flag_value = params.cnt_flag;
}

static void m_work_handler(struct k_work *work)
{
ARG_UNUSED(work);
mpsl_pm_utils_work_handler();
}

void 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;

mpsl_pm_init();
mpsl_pm_params_get(&params);
m_pm_prev_flag_value = params.cnt_flag;
m_pm_event_is_registered = false;
}
38 changes: 38 additions & 0 deletions tests/subsys/mpsl/pm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(pm_test)

# Generate runner for the test
test_runner_generate(pm_test.c)

# Create mocks for pm module.
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)

# Add test source file
target_sources(app PRIVATE pm_test.c)

# Include paths
target_include_directories(app PRIVATE src)

# Preinclude file to the UUT to redefine mpsl_work_schedule().
set_property(SOURCE ${ZEPHYR_NRF_MODULE_DIR}/subsys/mpsl/pm/mpsl_pm_utils.c
PROPERTY COMPILE_FLAGS "-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
)
Loading

0 comments on commit 2abf723

Please sign in to comment.