From bdba2e63f66eb8b9412e0bd26ec2a92e7aada260 Mon Sep 17 00:00:00 2001 From: Peter Bigot Date: Mon, 1 Feb 2021 16:42:52 -0600 Subject: [PATCH] samples: nrf: system_off: add RAM retention example for nRF52 Add an object to retain data across both reboots and entry to SYSTEM_OFF. This only works on nRF52, since nRF51 and nRF53 require different low-level operations to configure RAM retention. Signed-off-by: Peter Bigot --- samples/boards/nrf/system_off/CMakeLists.txt | 4 +- samples/boards/nrf/system_off/Kconfig | 14 ++ samples/boards/nrf/system_off/README.rst | 9 + samples/boards/nrf/system_off/prj.conf | 2 + samples/boards/nrf/system_off/sample.yaml | 8 +- samples/boards/nrf/system_off/src/main.c | 22 +++ samples/boards/nrf/system_off/src/retained.c | 177 +++++++++++++++++++ samples/boards/nrf/system_off/src/retained.h | 54 ++++++ 8 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 samples/boards/nrf/system_off/Kconfig create mode 100644 samples/boards/nrf/system_off/src/retained.c create mode 100644 samples/boards/nrf/system_off/src/retained.h diff --git a/samples/boards/nrf/system_off/CMakeLists.txt b/samples/boards/nrf/system_off/CMakeLists.txt index 7ac11a00679..6f5e65cd5fe 100644 --- a/samples/boards/nrf/system_off/CMakeLists.txt +++ b/samples/boards/nrf/system_off/CMakeLists.txt @@ -4,5 +4,5 @@ cmake_minimum_required(VERSION 3.13.1) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(nrf_system_off) -FILE(GLOB app_sources src/*.c) -target_sources(app PRIVATE ${app_sources}) +target_sources(app PRIVATE src/main.c) +target_sources_ifdef(CONFIG_APP_RETENTION app PRIVATE src/retained.c) diff --git a/samples/boards/nrf/system_off/Kconfig b/samples/boards/nrf/system_off/Kconfig new file mode 100644 index 00000000000..c73e6e666e6 --- /dev/null +++ b/samples/boards/nrf/system_off/Kconfig @@ -0,0 +1,14 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "Nordic SYSTEM_OFF demo" + +config APP_RETENTION + bool "Enable state retention in system off" + depends on SOC_COMPATIBLE_NRF52X + help + On some Nordic chips this application supports retaining + memory while in system off. Select this to enable the + feature. + +source "Kconfig.zephyr" diff --git a/samples/boards/nrf/system_off/README.rst b/samples/boards/nrf/system_off/README.rst index 5936309241a..00022708850 100644 --- a/samples/boards/nrf/system_off/README.rst +++ b/samples/boards/nrf/system_off/README.rst @@ -22,6 +22,15 @@ disable the deep sleep functionality before the kernel starts, which prevents the board from powering down during initialization of drivers that use unbounded delays to wait for startup. +RAM Retention +============= + +On nRF52 platforms this also can demonstrate RAM retention. By selecting +``CONFIG_APP_RETENTION=y`` state related to number of boots, number of times +system off was entered, and total uptime since initial power-on are retained +in a checksummed data structure. The POWER peripheral is configured to keep +the containing RAM section powered while in system-off mode. + Requirements ************ diff --git a/samples/boards/nrf/system_off/prj.conf b/samples/boards/nrf/system_off/prj.conf index 134a3f5f5a2..8eb237b76ea 100644 --- a/samples/boards/nrf/system_off/prj.conf +++ b/samples/boards/nrf/system_off/prj.conf @@ -2,3 +2,5 @@ CONFIG_PM=y # Required to disable default behavior of deep sleep on timeout CONFIG_PM_DEVICE=y CONFIG_GPIO=y +# Optional select RAM retention (nRF52 only) +#CONFIG_APP_RETENTION=y diff --git a/samples/boards/nrf/system_off/sample.yaml b/samples/boards/nrf/system_off/sample.yaml index f06c56e2bab..eccccc08408 100644 --- a/samples/boards/nrf/system_off/sample.yaml +++ b/samples/boards/nrf/system_off/sample.yaml @@ -1,7 +1,13 @@ sample: name: Low Power State Sample for nRF5x +common: + tags: power tests: sample.boards.nrf.system_off: build_only: true platform_allow: nrf52840dk_nrf52840 nrf52dk_nrf52832 nrf51dk_nrf51422 - tags: power + sample.boards.nrf.system_off.retained: + build_only: true + platform_allow: nrf52840dk_nrf52840 nrf52dk_nrf52832 + extra_configs: + - CONFIG_APP_RETENTION=y diff --git a/samples/boards/nrf/system_off/src/main.c b/samples/boards/nrf/system_off/src/main.c index ffe97641bf0..3d317aef434 100644 --- a/samples/boards/nrf/system_off/src/main.c +++ b/samples/boards/nrf/system_off/src/main.c @@ -9,6 +9,7 @@ #include #include #include +#include "retained.h" #include #define CONSOLE_LABEL DT_LABEL(DT_CHOSEN(zephyr_console)) @@ -40,6 +41,21 @@ void main(void) printk("\n%s system off demo\n", CONFIG_BOARD); + if (IS_ENABLED(CONFIG_APP_RETENTION)) { + bool retained_ok = retained_validate(); + + /* Increment for this boot attempt and update. */ + retained.boots += 1; + retained_update(); + + printk("Retained data: %s\n", retained_ok ? "valid" : "INVALID"); + printk("Boot count: %u\n", retained.boots); + printk("Off count: %u\n", retained.off_count); + printk("Active Ticks: %" PRIu64 "\n", retained.uptime_sum); + } else { + printk("Retained data not supported\n"); + } + /* Configure to generate PORT event (wakeup) on button 1 press. */ nrf_gpio_cfg_input(DT_GPIO_PIN(DT_NODELABEL(button0), gpios), NRF_GPIO_PIN_PULLUP); @@ -64,6 +80,12 @@ void main(void) printk("Entering system off; press BUTTON1 to restart\n"); + if (IS_ENABLED(CONFIG_APP_RETENTION)) { + /* Update the retained state */ + retained.off_count += 1; + retained_update(); + } + /* Above we disabled entry to deep sleep based on duration of * controlled delay. Here we need to override that, then * force entry to deep sleep on any delay. diff --git a/samples/boards/nrf/system_off/src/retained.c b/samples/boards/nrf/system_off/src/retained.c new file mode 100644 index 00000000000..1df7e0b797a --- /dev/null +++ b/samples/boards/nrf/system_off/src/retained.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include "retained.h" + +/* nRF52 RAM (really, RAM AHB slaves) are partitioned as: + * * Up to 8 blocks of two 4 KiBy byte "small" sections + * * A 9th block of with 32 KiBy "large" sections + * + * At time of writing the maximum number of large sections is 6, all + * within the first large block. Theoretically there could be more + * sections in the 9th block, and possibly more blocks. + */ + +/* Inclusive address of RAM start */ +#define SRAM_BEGIN (uintptr_t)DT_REG_ADDR(DT_NODELABEL(sram0)) + +/* Exclusive address of RAM end */ +#define SRAM_END (SRAM_BEGIN + (uintptr_t)DT_REG_SIZE(DT_NODELABEL(sram0))) + +/* Size of a controllable RAM section in the small blocks */ +#define SMALL_SECTION_SIZE 4096 + +/* Number of controllable RAM sections in each of the lower blocks */ +#define SMALL_SECTIONS_PER_BLOCK 2 + +/* Span of a small block */ +#define SMALL_BLOCK_SIZE (SMALL_SECTIONS_PER_BLOCK * SMALL_SECTION_SIZE) + +/* Number of small blocks */ +#define SMALL_BLOCK_COUNT 8 + +/* Span of the SRAM area covered by small sections */ +#define SMALL_SECTION_SPAN (SMALL_BLOCK_COUNT * SMALL_BLOCK_SIZE) + +/* Inclusive address of the RAM range covered by large sections */ +#define LARGE_SECTION_BEGIN (SRAM_BEGIN + SMALL_SECTION_SPAN) + +/* Size of a controllable RAM section in large blocks */ +#define LARGE_SECTION_SIZE 32768 + +/* Set or clear RAM retention in SYSTEM_OFF for the provided object. + * + * @note This only works for nRF52 with the POWER module. The other + * Nordic chips use a different low-level API, which is not currently + * used by this function. + * + * @param ptr pointer to the start of the retainable object + * + * @param len length of the retainable object + * + * @param enable true to enable retention, false to clear retention + */ +static int ram_range_retain(const void *ptr, + size_t len, + bool enable) +{ + uintptr_t addr = (uintptr_t)ptr; + uintptr_t addr_end = addr + len; + + /* Error if the provided range is empty or doesn't lie + * entirely within the SRAM address space. + */ + if ((len == 0U) + || (addr < SRAM_BEGIN) + || (addr > (SRAM_END - len))) { + return -EINVAL; + } + + /* Iterate over each section covered by the range, setting the + * corresponding RAM OFF retention bit in the parent block. + */ + do { + uintptr_t block_base = SRAM_BEGIN; + uint32_t section_size = SMALL_SECTION_SIZE; + uint32_t sections_per_block = SMALL_SECTIONS_PER_BLOCK; + bool is_large = (addr >= LARGE_SECTION_BEGIN); + uint8_t block = 0; + + if (is_large) { + block = 8; + block_base = LARGE_SECTION_BEGIN; + section_size = LARGE_SECTION_SIZE; + + /* RAM[x] supports only 16 sections, each its own bit + * for POWER (0..15) and RETENTION (16..31). We don't + * know directly how many sections are present, so + * assume they all are; the true limit will be + * determined by the SRAM size. + */ + sections_per_block = 16; + } + + uint32_t section = (addr - block_base) / section_size; + + if (section >= sections_per_block) { + block += section / sections_per_block; + section %= sections_per_block; + } + + uint32_t section_mask = + (POWER_RAM_POWERSET_S0RETENTION_On + << (section + POWER_RAM_POWERSET_S0RETENTION_Pos)); + + if (enable) { + nrf_power_rampower_mask_on(NRF_POWER, block, section_mask); + } else { + nrf_power_rampower_mask_off(NRF_POWER, block, section_mask); + } + + /* Move to the first address in the next section. */ + addr += section_size - (addr % section_size); + } while (addr < addr_end); + + return 0; +} + +/* Retained data must be defined in a no-init section to prevent the C + * runtime initialization from zeroing it before anybody can see it. + */ +__noinit struct retained_data retained; + +#define RETAINED_CRC_OFFSET offsetof(struct retained_data, crc) +#define RETAINED_CHECKED_SIZE (RETAINED_CRC_OFFSET + sizeof(retained.crc)) + +bool retained_validate(void) +{ + /* The residue of a CRC is what you get from the CRC over the + * message catenated with its CRC. This is the post-final-xor + * residue for CRC-32 (CRC-32/ISO-HDLC) which Zephyr calls + * crc32_ieee. + */ + const uint32_t residue = 0x2144df1c; + uint32_t crc = crc32_ieee((const uint8_t *)&retained, + RETAINED_CHECKED_SIZE); + bool valid = (crc == residue); + + /* If the CRC isn't valid, reset the retained data. */ + if (!valid) { + memset(&retained, 0, sizeof(retained)); + } + + /* Reset to accrue runtime from this session. */ + retained.uptime_latest = 0; + + /* Reconfigure to retain the state during system off, regardless of + * whether validation succeeded. Although these values can sometimes + * be observed to be preserved across System OFF, the product + * specification states they are not retained in that situation, and + * that can also be observed. + */ + (void)ram_range_retain(&retained, RETAINED_CHECKED_SIZE, true); + + return valid; +} + +void retained_update(void) +{ + uint64_t now = k_uptime_ticks(); + + retained.uptime_sum += (now - retained.uptime_latest); + retained.uptime_latest = now; + + uint32_t crc = crc32_ieee((const uint8_t *)&retained, + RETAINED_CRC_OFFSET); + + retained.crc = sys_cpu_to_le32(crc); +} diff --git a/samples/boards/nrf/system_off/src/retained.h b/samples/boards/nrf/system_off/src/retained.h new file mode 100644 index 00000000000..ebe3b2f2ec2 --- /dev/null +++ b/samples/boards/nrf/system_off/src/retained.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef RETAINED_H_ +#define RETAINED_H_ + +#include + +/* Example of validatable retained data. */ +struct retained_data { + /* The uptime from the current session the last time the + * retained data was updated. + */ + uint64_t uptime_latest; + + /* Cumulative uptime from all previous sessions up through + * uptime_latest of this session. + */ + uint64_t uptime_sum; + + /* Number of times the application has started. */ + uint32_t boots; + + /* Number of times the application has gone into system off. */ + uint32_t off_count; + + /* CRC used to validate the retained data. This must be + * stored little-endian, and covers everything up to but not + * including this field. + */ + uint32_t crc; +}; + +/* For simplicity in the sample just allow anybody to see and + * manipulate the retained state. + */ +extern struct retained_data retained; + +/* Check whether the retained data is valid, and if not reset it. + * + * @return true if and only if the data was valid and reflects state + * from previous sessions. + */ +bool retained_validate(void); + +/* Update any generic retained state and recalculate its checksum so + * subsequent boots can verify the retained state. + */ +void retained_update(void); + +#endif /* RETAINED_H_ */