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