diff --git a/soc/nordic/common/CMakeLists.txt b/soc/nordic/common/CMakeLists.txt index 4808319557c..8c00b768b90 100644 --- a/soc/nordic/common/CMakeLists.txt +++ b/soc/nordic/common/CMakeLists.txt @@ -34,3 +34,4 @@ if(CONFIG_TFM_PARTITION_PLATFORM) endif() zephyr_library_sources_ifdef(CONFIG_NRF_SYS_EVENT nrf_sys_event.c) +zephyr_library_sources_ifdef(CONFIG_MRAM_LATENCY mram_latency.c) diff --git a/soc/nordic/common/Kconfig b/soc/nordic/common/Kconfig index ef32e7403c1..9b927d54432 100644 --- a/soc/nordic/common/Kconfig +++ b/soc/nordic/common/Kconfig @@ -8,4 +8,20 @@ config NRF_SYS_EVENT bool "nRF system event support" select NRFX_POWER if !NRF_PLATFORM_HALTIUM +config MRAM_LATENCY + bool "MRAM latency manager" + depends on NRFS_HAS_MRAM_SERVICE + select ONOFF + select NRFS_MRAM_SERVICE_ENABLED + +config MRAM_LATENCY_SYNC_TIMEOUT + int "Timeout in synchronous request" + default 1000 + help + Timeout is given in milliseconds. + +module = MRAM_LATENCY +module-str = mram_latency +source "subsys/logging/Kconfig.template.log_config" + rsource "vpr/Kconfig" diff --git a/soc/nordic/common/mram_latency.c b/soc/nordic/common/mram_latency.c new file mode 100644 index 00000000000..0bfc6d4bfe2 --- /dev/null +++ b/soc/nordic/common/mram_latency.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +LOG_MODULE_REGISTER(mram_latency, CONFIG_MRAM_LATENCY_LOG_LEVEL); + +enum mram_latency_state { + MRAM_LATENCY_OFF = 0, + MRAM_LATENCY_OFF_PENDING, + MRAM_LATENCY_ON, +}; + +static struct k_work work; +static bool no_latency; +static enum mram_latency_state state; + +static onoff_notify_fn onoff_notify; +struct onoff_manager mram_latency_mgr; + +struct sync_latency_req { + struct onoff_client cli; + struct k_sem sem; + int res; +}; + +static void latency_change_req(bool latency_not_allowed) +{ + nrfs_err_t err; + + if (latency_not_allowed) { + err = nrfs_mram_set_latency(MRAM_LATENCY_NOT_ALLOWED, NULL); + if (err != NRFS_SUCCESS) { + onoff_notify(&mram_latency_mgr, -EIO); + } + } else { + /* There is no event for that setting so we can notify onoff manager + * immediately. + */ + err = nrfs_mram_set_latency(MRAM_LATENCY_ALLOWED, NULL); + onoff_notify(&mram_latency_mgr, err != NRFS_SUCCESS ? -EIO : 0); + } +} + +static void latency_change(bool latency_not_allowed) +{ + LOG_DBG("Request: latency %s allowed", latency_not_allowed ? "not " : ""); + if (state == MRAM_LATENCY_OFF) { + state = MRAM_LATENCY_OFF_PENDING; + } else if (k_is_in_isr()) { + /* nrfs cannot be called from interrupt context so defer to the work + * queue context and execute from there. + */ + no_latency = latency_not_allowed; + k_work_submit(&work); + } else { + latency_change_req(latency_not_allowed); + } +} + +static void no_latency_start(struct onoff_manager *mgr, onoff_notify_fn notify) +{ + onoff_notify = notify; + latency_change(true); +} + +static void no_latency_stop(struct onoff_manager *mgr, onoff_notify_fn notify) +{ + latency_change(false); +} + +static void evt_handler(nrfs_mram_latency_evt_t const *p_evt, void *context) +{ + int res = p_evt->type == NRFS_MRAM_LATENCY_REQ_APPLIED ? 0 : -EIO; + + LOG_DBG("Latency not allowed - applied"); + onoff_notify(&mram_latency_mgr, res); +} + +static void work_handler(struct k_work *work) +{ + latency_change_req(no_latency); +} + +int mram_no_latency_cancel_or_release(struct onoff_client *cli) +{ + return onoff_cancel_or_release(&mram_latency_mgr, cli); +} + +int mram_no_latency_request(struct onoff_client *cli) +{ + return onoff_request(&mram_latency_mgr, cli); +} + +static void sync_req_cb(struct onoff_manager *mgr, struct onoff_client *cli, uint32_t state, + int res) +{ + struct sync_latency_req *req = CONTAINER_OF(cli, struct sync_latency_req, cli); + + req->res = res; + k_sem_give(&req->sem); +} + +int mram_no_latency_sync_request(void) +{ + struct sync_latency_req req; + int rv; + + if (k_is_in_isr() || (state != MRAM_LATENCY_ON)) { + return -ENOTSUP; + } + + k_sem_init(&req.sem, 0, 1); + sys_notify_init_callback(&req.cli.notify, sync_req_cb); + rv = onoff_request(&mram_latency_mgr, &req.cli); + if (rv < 0) { + return rv; + } + + rv = k_sem_take(&req.sem, K_MSEC(CONFIG_MRAM_LATENCY_SYNC_TIMEOUT)); + if (rv < 0) { + return rv; + } + + return req.res; +} + +int mram_no_latency_sync_release(void) +{ + return onoff_release(&mram_latency_mgr) >= 0 ? 0 : -EIO; +} + +/* First initialize onoff manager to be able to accept requests. */ +static int init_manager(void) +{ + static const struct onoff_transitions transitions = + ONOFF_TRANSITIONS_INITIALIZER(no_latency_start, no_latency_stop, NULL); + + return onoff_manager_init(&mram_latency_mgr, &transitions); +} + +/* When kernel and IPC is running initialize nrfs. Optionally, execute pending request. */ +static int init_nrfs(void) +{ + nrfs_err_t err; + int rv; + + err = nrfs_backend_wait_for_connection(K_FOREVER); + if (err != NRFS_SUCCESS) { + return -EIO; + } + + err = nrfs_mram_init(evt_handler); + if (err != NRFS_SUCCESS) { + return -EIO; + } + + k_work_init(&work, work_handler); + + if (state == MRAM_LATENCY_OFF_PENDING) { + latency_change(true); + } + + state = MRAM_LATENCY_ON; + + return rv; +} + +SYS_INIT(init_manager, PRE_KERNEL_1, 0); +SYS_INIT(init_nrfs, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/soc/nordic/common/mram_latency.h b/soc/nordic/common/mram_latency.h new file mode 100644 index 00000000000..1331125b02a --- /dev/null +++ b/soc/nordic/common/mram_latency.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * nRF SoC specific public APIs for MRAM latency management + */ + +#ifndef SOC_NORDIC_COMMON_MRAM_LATENCY_H_ +#define SOC_NORDIC_COMMON_MRAM_LATENCY_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @internal For test purposes only. */ +extern struct onoff_manager mram_latency_mgr; + +/** @brief Request MRAM operations without latency. + * + * The return value indicates the success or failure of an attempt to initiate + * an operation to request the MRAM low latency. If initiation of the + * operation succeeds, the result of the request operation is provided through + * the configured client notification method, possibly before this call returns. + * + * @param cli pointer to client state providing instructions on synchronous + * expectations and how to notify the client when the request + * completes. Behavior is undefined if client passes a pointer + * object associated with an incomplete service operation. + * + * @retval non-negative the observed state of the on-off service associated + * with the MRAM latency service. + * @retval -EIO if MRAM latency service returned error. + * @retval -EINVAL if the parameters are invalid. + * @retval -EAGAIN if the reference count would overflow. + */ +int mram_no_latency_request(struct onoff_client *cli); + +/** @brief Request MRAM operations without latency. + * + * Request is synchronous and blocks until it is completed. It can be called only + * from the thread context and cannot be called in the pre kernel stage. + * + * @retval 0 on successful request. + * @retval -EIO if MRAM latency service returned error. + * @retval -EAGAIN if request was not completed on time. + */ +int mram_no_latency_sync_request(void); + +/** + * @brief Safely cancel a request for MRAM operations without latency. + * + * It may be that a client has issued a reservation request but needs to + * shut down before the request has completed. This function attempts to + * cancel the request and issues a release if cancellation fails because + * the request was completed. This synchronously ensures that ownership + * data reverts to the client so is available for a future request. + * + * @param cli a pointer to the same client state that was provided + * when the operation to be cancelled was issued. + * + * @retval ONOFF_STATE_TO_ON if the cancellation occurred before the transition + * completed. + * @retval ONOFF_STATE_ON if the cancellation occurred after the transition + * completed. + * @retval -EINVAL if the parameters are invalid. + * @retval -EIO if MRAM latency service returned error. + * @retval negative other errors produced by onoff_release(). + */ +int mram_no_latency_cancel_or_release(struct onoff_client *cli); + +/** + * @brief Release a request for MRAM operations without latency. + * + * It should match with a completed @ref mram_no_latency_sync_request call. + * + * @retval 0 on successful request. + * @retval -EIO if MRAM latency service returned error. + */ +int mram_no_latency_sync_release(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SOC_NORDIC_COMMON_MRAM_LATENCY_H_ */ diff --git a/soc/nordic/nrf54h/CMakeLists.txt b/soc/nordic/nrf54h/CMakeLists.txt index 10c59b04583..7edc4d43ea1 100644 --- a/soc/nordic/nrf54h/CMakeLists.txt +++ b/soc/nordic/nrf54h/CMakeLists.txt @@ -12,8 +12,6 @@ zephyr_library_sources_ifdef(CONFIG_PM_S2RAM pm_s2ram.c) zephyr_include_directories(.) -zephyr_library_sources_ifdef(CONFIG_SOC_NRF54H20_NO_MRAM_LATENCY mram.c) - # Ensure that image size aligns with 16 bytes so that MRAMC finalizes all writes # for the image correctly zephyr_linker_sources(SECTIONS SORT_KEY zzz_place_align_at_end align.ld) diff --git a/soc/nordic/nrf54h/Kconfig b/soc/nordic/nrf54h/Kconfig index a92b176fa9d..1b667e25985 100644 --- a/soc/nordic/nrf54h/Kconfig +++ b/soc/nordic/nrf54h/Kconfig @@ -77,9 +77,3 @@ config SOC_NRF54H20_ENGB_CPUFLPR depends on RISCV_CORE_NORDIC_VPR rsource "gpd/Kconfig" - -config SOC_NRF54H20_NO_MRAM_LATENCY - bool "Disallow MRAM latency" - imply NRFS - imply NRFS_MRAM_SERVICE_ENABLED - default y if SOC_NRF54H20_CPUAPP || SOC_NRF54H20_ENGB_CPUAPP diff --git a/soc/nordic/nrf54h/mram.c b/soc/nordic/nrf54h/mram.c deleted file mode 100644 index 5549a2437df..00000000000 --- a/soc/nordic/nrf54h/mram.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2024 Nordic Semiconductor ASA. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include - -#include -#include - -LOG_MODULE_REGISTER(mram, CONFIG_SOC_LOG_LEVEL); - -static void mram_latency_handler(nrfs_mram_latency_evt_t const *p_evt, void *context) -{ - ARG_UNUSED(context); - - switch (p_evt->type) { - case NRFS_MRAM_LATENCY_REQ_APPLIED: - LOG_DBG("MRAM latency not allowed setting applied"); - break; - case NRFS_MRAM_LATENCY_REQ_REJECTED: - LOG_ERR("MRAM latency not allowed setting rejected"); - k_panic(); - break; - default: - LOG_WRN("Unexpected event: %d", p_evt->type); - } -} - -/* Turn off mram automatic suspend as it causes delays in time depended code sections. */ -static int turn_off_suspend_mram(void) -{ - nrfs_err_t err; - - /* Wait for ipc initialization */ - nrfs_backend_wait_for_connection(K_FOREVER); - - err = nrfs_mram_init(mram_latency_handler); - if (err != NRFS_SUCCESS) { - LOG_ERR("MRAM service init failed: %d", err); - return -EIO; - } - - LOG_DBG("MRAM service initialized, disallow latency"); - - err = nrfs_mram_set_latency(MRAM_LATENCY_NOT_ALLOWED, NULL); - if (err != NRFS_SUCCESS) { - LOG_ERR("MRAM: set latency failed (%d)", err); - return -EIO; - } - - return 0; -} - -SYS_INIT(turn_off_suspend_mram, APPLICATION, 90); diff --git a/tests/boards/nrf/mram_latency/CMakeLists.txt b/tests/boards/nrf/mram_latency/CMakeLists.txt new file mode 100644 index 00000000000..4ac7b855575 --- /dev/null +++ b/tests/boards/nrf/mram_latency/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(mram_latency) + +FILE(GLOB app_sources src/*.c) + +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/boards/nrf/mram_latency/prj.conf b/tests/boards/nrf/mram_latency/prj.conf new file mode 100644 index 00000000000..ed9ce9820c1 --- /dev/null +++ b/tests/boards/nrf/mram_latency/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +CONFIG_MRAM_LATENCY=y diff --git a/tests/boards/nrf/mram_latency/src/main.c b/tests/boards/nrf/mram_latency/src/main.c new file mode 100644 index 00000000000..b7110dd7ae3 --- /dev/null +++ b/tests/boards/nrf/mram_latency/src/main.c @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +LOG_MODULE_REGISTER(test); + +#include + +#define TIMEOUT_MS 10 + +static volatile uint32_t current_state; +static struct onoff_monitor monitor; +static struct onoff_client early_client; +static int early_rv; +static int early_result; + +struct test_req { + struct onoff_client cli; + struct k_sem sem; + int res; + uint32_t state; +}; + +static void basic_cb(struct onoff_manager *mgr, struct onoff_client *cli, uint32_t state, int res) +{ + struct test_req *req = CONTAINER_OF(cli, struct test_req, cli); + + req->res = res; + req->state = state; + k_sem_give(&req->sem); +} + +static void monitor_cb(struct onoff_manager *mgr, struct onoff_monitor *mon, uint32_t state, + int res) +{ + current_state = state; +} + +ZTEST(mram_latency, test_basic_requests) +{ + struct test_req req1, req2; + uint32_t exp_state; + int rv; + + k_sem_init(&req1.sem, 0, 1); + k_sem_init(&req2.sem, 0, 1); + + sys_notify_init_callback(&req1.cli.notify, basic_cb); + exp_state = ONOFF_STATE_OFF; + /* Req: 0->1 trigger to on */ + rv = mram_no_latency_request(&req1.cli); + zassert_equal(rv, exp_state, "Unexpected rv:%d (exp:%d)", rv, exp_state); + + sys_notify_init_callback(&req2.cli.notify, basic_cb); + exp_state = ONOFF_STATE_TO_ON; + /* Req: 1->2 */ + rv = mram_no_latency_request(&req2.cli); + zassert_equal(rv, exp_state, "Unexpected rv:%d (exp:%d)", rv, exp_state); + + rv = k_sem_take(&req1.sem, K_MSEC(TIMEOUT_MS)); + zassert_equal(rv, 0, "Unexpected rv:%d", rv); + zassert_equal(req1.res, 0, "Unexpected res:%d", req1.res); + zassert_equal(req1.state, ONOFF_STATE_ON, "Unexpected state:%08x", req1.state); + + rv = k_sem_take(&req2.sem, K_MSEC(TIMEOUT_MS)); + zassert_equal(rv, 0, "Unexpected rv:%d", rv); + zassert_equal(req2.res, 0, "Unexpected res:%d", req2.res); + zassert_equal(req2.state, ONOFF_STATE_ON); + + exp_state = ONOFF_STATE_ON; + rv = mram_no_latency_cancel_or_release(&req2.cli); + /* Req: 2->1 */ + zassert_equal(rv, exp_state, "Unexpected rv:%d (exp:%d)", rv, exp_state); + + /* Req: 1->0 going to off triggered*/ + rv = mram_no_latency_cancel_or_release(&req1.cli); + zassert_equal(rv, exp_state, "Unexpected rv:%d (exp:%d)", rv, exp_state); + + sys_notify_init_callback(&req1.cli.notify, basic_cb); + exp_state = ONOFF_STATE_OFF; + + /* Req: 0->1 triggered to on while in to off. */ + rv = mram_no_latency_request(&req1.cli); + zassert_equal(rv, exp_state, "Unexpected rv:%d (exp:%d)", rv, exp_state); + + /* Req: 1->0 releases which to off. */ + exp_state = ONOFF_STATE_TO_ON; + rv = mram_no_latency_cancel_or_release(&req1.cli); + zassert_equal(rv, exp_state, "Unexpected rv:%d (exp:%d)", rv, exp_state); + + k_msleep(10); +} + +static void timeout(struct k_timer *timer) +{ + struct test_req *req = k_timer_user_data_get(timer); + uint32_t exp_state; + int rv; + + sys_notify_init_callback(&req->cli.notify, basic_cb); + exp_state = ONOFF_STATE_OFF; + rv = mram_no_latency_request(&req->cli); + zassert_equal(rv, exp_state, "Unexpected rv:%d (exp:%d)", rv, exp_state); +} + +ZTEST(mram_latency, test_req_from_irq) +{ + struct test_req req; + struct k_timer timer; + uint32_t exp_state; + int rv; + + k_sem_init(&req.sem, 0, 1); + k_timer_init(&timer, timeout, NULL); + k_timer_user_data_set(&timer, &req); + /* Start k_timer and from that context request MRAM latency. */ + k_timer_start(&timer, K_MSEC(1), K_NO_WAIT); + + exp_state = ONOFF_STATE_ON; + rv = k_sem_take(&req.sem, K_MSEC(TIMEOUT_MS)); + zassert_equal(rv, 0, "Unexpected rv:%d", rv); + zassert_equal(req.res, 0, "Unexpected res:%d", req.res); + zassert_equal(req.state, exp_state); + + rv = mram_no_latency_cancel_or_release(&req.cli); + zassert_equal(rv, exp_state, "Unexpected rv:%d (exp:%d)", rv, exp_state); +} + +ZTEST(mram_latency, test_sync_req) +{ + zassert_equal(current_state, ONOFF_STATE_OFF); + mram_no_latency_sync_request(); + zassert_equal(current_state, ONOFF_STATE_ON); + mram_no_latency_sync_release(); + zassert_equal(current_state, ONOFF_STATE_OFF); +} + +ZTEST(mram_latency, test_early_req) +{ + zassert_true(early_rv >= 0); + zassert_true(early_result >= 0); +} + +static void *setup(void) +{ + int rv; + + monitor.callback = monitor_cb; + rv = onoff_monitor_register(&mram_latency_mgr, &monitor); + zassert_equal(rv, 0); + + if (early_rv >= 0) { + sys_notify_fetch_result(&early_client.notify, &early_result); + } + + mram_no_latency_cancel_or_release(&early_client); + + return NULL; +} + +static void before(void *arg) +{ + zassert_equal(current_state, ONOFF_STATE_OFF); +} + +static void after(void *arg) +{ + zassert_equal(current_state, ONOFF_STATE_OFF); +} + +static int early_mram_client(void) +{ + sys_notify_init_spinwait(&early_client.notify); + early_rv = mram_no_latency_request(&early_client); + + return 0; +} + +SYS_INIT(early_mram_client, PRE_KERNEL_2, 0); + +ZTEST_SUITE(mram_latency, NULL, setup, before, after, NULL); diff --git a/tests/boards/nrf/mram_latency/testcase.yaml b/tests/boards/nrf/mram_latency/testcase.yaml new file mode 100644 index 00000000000..691370a5618 --- /dev/null +++ b/tests/boards/nrf/mram_latency/testcase.yaml @@ -0,0 +1,12 @@ +common: + tags: drivers + harness: ztest + +tests: + boards.nrf.mram_latency: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + - nrf54h20dk/nrf54h20/cpurad + integration_platforms: + - nrf54h20dk/nrf54h20/cpuapp + - nrf54h20dk/nrf54h20/cpurad