1.. _module-pw_persistent_ram: 2 3================= 4pw_persistent_ram 5================= 6The ``pw_persistent_ram`` module contains utilities and containers for using 7persistent RAM. By persistent RAM we are referring to memory which is not 8initialized across reboots by the hardware nor bootloader(s). This memory may 9decay or bit rot between reboots including brownouts, ergo integrity checking is 10highly recommended. 11 12.. Note:: 13 This is something that not all architectures and applications built on them 14 support and requires hardware in the loop testing to verify it works as 15 intended. 16 17.. Warning:: 18 Do not treat the current containers provided in this module as stable storage 19 primitives. We are still evaluating lighterweight checksums from a code size 20 point of view. In other words, future updates to this module may result in a 21 loss of persistent data across software updates. 22 23------------------------ 24Persistent RAM Placement 25------------------------ 26Persistent RAM is typically provided through specially carved out linker script 27sections and/or memory ranges which are located in such a way that any 28bootloaders and the application boot code do not clobber it. 29 301. If persistent linker sections are provided, we recommend using our section 31 placement macro. For example imagine the persistent section name is called 32 `.noinit`, then you could instantiate an object as such: 33 34 .. code-block:: cpp 35 36 #include "pw_persistent_ram/persistent.h" 37 #include "pw_preprocessor/compiler.h" 38 39 using pw::persistent_ram::Persistent; 40 41 PW_PLACE_IN_SECTION(".noinit") Persistent<bool> persistent_bool; 42 432. If persistent memory ranges are provided, we recommend using a struct to wrap 44 the different persisted objects. This then could be checked to fit in the 45 provided memory range size, for example by asserting against variables 46 provided through a linker script. 47 48 .. code-block:: cpp 49 50 #include "pw_assert/check.h" 51 #include "pw_persistent_ram/persistent.h" 52 53 // Provided for example through a linker script. 54 extern "C" uint8_t __noinit_begin; 55 extern "C" uint8_t __noinit_end; 56 57 struct PersistentData { 58 Persistent<bool> persistent_bool; 59 }; 60 PersistentData& persistent_data = 61 *reinterpret_cast<NoinitData*>(&__noinit_begin); 62 63 void CheckPersistentDataSize() { 64 PW_DCHECK_UINT_LE(sizeof(PersistentData), 65 __noinit_end - __noinit_begin, 66 "PersistentData overflowed the noinit memory range"); 67 } 68 69----------------------------------- 70Persistent RAM Lifecycle Management 71----------------------------------- 72In order for persistent RAM containers to be as useful as possible, any 73invalidation of persistent RAM and the containers therein should be executed 74before the global static C++ constructors, but after the BSS and data sections 75are initialized in RAM. 76 77The preferred way to clear Persistent RAM is to simply zero entire persistent 78RAM sections and/or memory regions. Pigweed's persistent containers have picked 79integrity checks which work with zeroed memory, meaning they do not hold a value 80after zeroing. Alternatively containers can be individually cleared. 81 82The boot sequence itself is tightly coupled to the number of persistent sections 83and/or memory regions which exist in the final image, ergo this is something 84which Pigweed cannot provide to the user directly. However, we do recommend 85following some guidelines: 86 871. Do not instantiate regular types/objects in persistent RAM, ensure integrity 88 checking is always used! This is a major risk with this technique and can 89 lead to unexpected memory corruption. 902. Always instantiate persistent containers outside of the objects which depend 91 on them and use dependency injection. This permits unit testing and avoids 92 placement accidents of persistents and/or their users. 933. Always erase persistent RAM data after software updates unless the 94 persistent storage containers are explicitly stored at fixed address and 95 with a fixed layout. This prevents use of swapped objects or their members 96 where the same integrity checks are used. 974. Consider zeroing persistent RAM to recover from crashes which may be induced 98 by persistent RAM usage, for example by checking the reboot/crash reason. 995. Consider zeroing persistent RAM on cold boots to always start from a 100 consistent state if persistence is only desired across warm reboots. This can 101 create determinism from cold boots when using for example DRAM. 1026. Consider an explicit persistent clear request which can be set before a warm 103 reboot as a signal to zero all persistent RAM on the next boot to emulate 104 persistent memory loss in a threadsafe manner. 105 106--------------------------------- 107pw::persistent_ram::Persistent<T> 108--------------------------------- 109The Persistent is a simple container for holding its templated value ``T`` with 110CRC16 integrity checking. Note that a Persistent will be lost if a write/set 111operation is interrupted or otherwise not completed, as it is not double 112buffered. 113 114The default constructor does nothing, meaning it will result in either invalid 115state initially or a valid persisted value from a previous session. 116 117The destructor does nothing, ergo it is okay if it is not executed during 118shutdown. 119 120Example: Storing an integer 121--------------------------- 122A common use case of persistent data is to track boot counts, or effectively 123how often the device has rebooted. This can be useful for monitoring how many 124times the device rebooted and/or crashed. This can be easily accomplished using 125the Persistent container. 126 127.. code-block:: cpp 128 129 #include "pw_persistent_ram/persistent.h" 130 #include "pw_preprocessor/compiler.h" 131 132 using pw::persistent_ram::Persistent; 133 134 class BootCount { 135 public: 136 explicit BootCount(Persistent<uint16_t>& persistent_boot_count) 137 : persistent_(persistent_boot_count) { 138 if (!persistent_.has_value()) { 139 persistent_ = 0; 140 } else { 141 persistent_ = persistent_.value() + 1; 142 } 143 boot_count_ = persistent_.value(); 144 } 145 146 uint16_t GetBootCount() { return boot_count_; } 147 148 private: 149 Persistent<uint16_t>& persistent_; 150 uint16_t boot_count_; 151 }; 152 153 PW_PLACE_IN_SECTION(".noinit") Persistent<uint16_t> persistent_boot_count; 154 BootCount boot_count(persistent_boot_count); 155 156 int main() { 157 const uint16_t boot_count = boot_count.GetBootCount(); 158 // ... rest of main 159 } 160 161Example: Storing larger objects 162------------------------------- 163Larger objects may be inefficient to copy back and forth due to the need for 164a working copy. To work around this, you can get a Mutator handle that provides 165direct access to the underlying object. As long as the Mutator is in scope, it 166is invalid to access the underlying Persistent, but you'll be able to directly 167modify the object in place. Once the Mutator goes out of scope, the Persistent 168object's checksum is updated to reflect the changes. 169 170.. code-block:: cpp 171 172 #include "pw_persistent_ram/persistent.h" 173 #include "pw_preprocessor/compiler.h" 174 175 using pw::persistent_ram::Persistent; 176 177 contexpr size_t kMaxReasonLength = 256; 178 179 struct LastCrashInfo { 180 uint32_t uptime_ms; 181 uint32_t boot_id; 182 char reason[kMaxReasonLength]; 183 } 184 185 PW_PLACE_IN_SECTION(".noinit") Persistent<LastBootInfo> persistent_crash_info; 186 187 void HandleCrash(const char* fmt, va_list args) { 188 // Once this scope ends, we know the persistent object has been updated 189 // to reflect changes. 190 { 191 auto& mutable_crash_info = 192 persistent_crash_info.mutator(GetterAction::kReset); 193 vsnprintf(mutable_crash_info->reason, 194 sizeof(mutable_crash_info->reason), 195 fmt, 196 args); 197 mutable_crash_info->uptime_ms = system::GetUptimeMs(); 198 mutable_crash_info->boot_id = system::GetBootId(); 199 } 200 // ... 201 } 202 203 int main() { 204 if (persistent_crash_info.has_value()) { 205 LogLastCrashInfo(persistent_crash_info.value()); 206 // Clear crash info once it has been dumped. 207 persistent_crash_info.Invalidate(); 208 } 209 210 // ... rest of main 211 } 212 213.. _module-pw_persistent_ram-persistent_buffer: 214 215------------------------------------ 216pw::persistent_ram::PersistentBuffer 217------------------------------------ 218The PersistentBuffer is a persistent storage container for variable-length 219serialized data. Rather than allowing direct access to the underlying buffer for 220random-access mutations, the PersistentBuffer is mutable through a 221PersistentBufferWriter that implements the pw::stream::Writer interface. This 222removes the potential for logical errors due to RAII or open()/close() semantics 223as both the PersistentBuffer and PersistentBufferWriter can be used validly as 224long as their access is serialized. 225 226Example 227------- 228An example use case is emitting crash handler logs to a buffer for them to be 229available after a the device reboots. Once the device reboots, the logs would be 230emitted by the logging system. While this isn't always practical for plaintext 231logs, tokenized logs are small enough for this to be useful. 232 233.. code-block:: cpp 234 235 #include "pw_persistent_ram/persistent_buffer.h" 236 #include "pw_preprocessor/compiler.h" 237 238 using pw::persistent_ram::PersistentBuffer; 239 using pw::persistent_ram::PersistentBuffer::PersistentBufferWriter; 240 241 PW_KEEP_IN_SECTION(".noinit") PersistentBuffer<2048> crash_logs; 242 void CheckForCrashLogs() { 243 if (crash_logs.has_value()) { 244 // A function that dumps sequentially serialized logs using pw_log. 245 DumpRawLogs(crash_logs.written_data()); 246 crash_logs.clear(); 247 } 248 } 249 250 void HandleCrash(CrashInfo* crash_info) { 251 PersistentBufferWriter crash_log_writer = crash_logs.GetWriter(); 252 // Sets the pw::stream::Writer that pw_log should dump logs to. 253 crash_log_writer.clear(); 254 SetLogSink(crash_log_writer); 255 // Handle crash, calling PW_LOG to log useful info. 256 } 257 258 int main() { 259 void CheckForCrashLogs(); 260 // ... rest of main 261 } 262 263Size Report 264----------- 265The following size report showcases the overhead for using Persistent. Note that 266this is templating the Persistent only on a ``uint32_t``, ergo the cost without 267pw_checksum's CRC16 is the approximate cost per type. 268 269.. include:: persistent_size 270 271Compatibility 272------------- 273* C++17 274 275Dependencies 276------------ 277* ``pw_checksum`` 278