Browse Source
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 <peter.bigot@nordicsemi.no>pull/32396/head
8 changed files with 287 additions and 3 deletions
@ -0,0 +1,14 @@
@@ -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" |
@ -1,7 +1,13 @@
@@ -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 |
||||
|
@ -0,0 +1,177 @@
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Nordic Semiconductor ASA |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
#include <string.h> |
||||
#include <zephyr.h> |
||||
#include <devicetree.h> |
||||
#include <sys/byteorder.h> |
||||
#include <sys/crc.h> |
||||
#include <hal/nrf_power.h> |
||||
#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); |
||||
} |
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2021 Nordic Semiconductor ASA |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
#ifndef RETAINED_H_ |
||||
#define RETAINED_H_ |
||||
|
||||
#include <inttypes.h> |
||||
|
||||
/* 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_ */ |
Loading…
Reference in new issue