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