Ewout van Bekkum | 32dc5c5 | 2021-03-16 11:35:37 -0700 | [diff] [blame] | 1 | .. _module-pw_persistent_ram: |
| 2 | |
| 3 | ================= |
| 4 | pw_persistent_ram |
| 5 | ================= |
| 6 | The ``pw_persistent_ram`` module contains utilities and containers for using |
| 7 | persistent RAM. By persistent RAM we are referring to memory which is not |
| 8 | initialized across reboots by the hardware nor bootloader(s). This memory may |
| 9 | decay or bit rot between reboots including brownouts, ergo integrity checking is |
| 10 | highly 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 | ------------------------ |
| 24 | Persistent RAM Placement |
| 25 | ------------------------ |
| 26 | Persistent RAM is typically provided through specially carved out linker script |
| 27 | sections and/or memory ranges which are located in such a way that any |
| 28 | bootloaders and the application boot code do not clobber it. |
| 29 | |
| 30 | 1. 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_KEEP_IN_SECTION(".noinit") Persistent<bool> persistent_bool; |
| 42 | |
| 43 | 2. 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 | |
Wyatt Hepler | f298de4 | 2021-03-19 15:06:36 -0700 | [diff] [blame] | 50 | #include "pw_assert/check.h" |
Ewout van Bekkum | 32dc5c5 | 2021-03-16 11:35:37 -0700 | [diff] [blame] | 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 | ----------------------------------- |
| 70 | Persistent RAM Lifecycle Management |
| 71 | ----------------------------------- |
| 72 | In order for persistent RAM containers to be as useful as possible, any |
| 73 | invalidation of persistent RAM and the containers therein should be executed |
| 74 | before the global static C++ constructors, but after the BSS and data sections |
| 75 | are initialized in RAM. |
| 76 | |
| 77 | The preferred way to clear Persistent RAM is to simply zero entire persistent |
Shiva Rajagopal | 9e51656 | 2021-05-11 17:04:15 -0700 | [diff] [blame] | 78 | RAM sections and/or memory regions. Pigweed's persistent containers have picked |
| 79 | integrity checks which work with zeroed memory, meaning they do not hold a value |
Ewout van Bekkum | 32dc5c5 | 2021-03-16 11:35:37 -0700 | [diff] [blame] | 80 | after zeroing. Alternatively containers can be individually cleared. |
| 81 | |
| 82 | The boot sequence itself is tightly coupled to the number of persistent sections |
| 83 | and/or memory regions which exist in the final image, ergo this is something |
| 84 | which Pigweed cannot provide to the user directly. However, we do recommend |
| 85 | following some guidelines: |
| 86 | |
| 87 | 1. 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. |
| 90 | 2. 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. |
| 93 | 3. 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. |
| 97 | 4. 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. |
| 99 | 5. 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. |
| 102 | 6. 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 | --------------------------------- |
| 107 | pw::persistent_ram::Persistent<T> |
| 108 | --------------------------------- |
| 109 | The Persistent is a simple container for holding its templated value ``T`` with |
| 110 | CRC16 integrity checking. Note that a Persistent will be lost if a write/set |
| 111 | operation is interrupted or otherwise not completed, as it is not double |
| 112 | buffered. |
| 113 | |
| 114 | The default constructor does nothing, meaning it will result in either invalid |
| 115 | state initially or a valid persisted value from a previous session. |
| 116 | |
| 117 | The destructor does nothing, ergo it is okay if it is not executed during |
| 118 | shutdown. |
| 119 | |
Armando Montanez | 9b085ce | 2021-03-19 15:12:25 -0700 | [diff] [blame] | 120 | Example: Storing an integer |
| 121 | --------------------------- |
Ewout van Bekkum | 32dc5c5 | 2021-03-16 11:35:37 -0700 | [diff] [blame] | 122 | A common use case of persistent data is to track boot counts, or effectively |
| 123 | how often the device has rebooted. This can be useful for monitoring how many |
| 124 | times the device rebooted and/or crashed. This can be easily accomplished using |
| 125 | the 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_KEEP_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 | |
Armando Montanez | 9b085ce | 2021-03-19 15:12:25 -0700 | [diff] [blame] | 161 | Example: Storing larger objects |
| 162 | ------------------------------- |
| 163 | Larger objects may be inefficient to copy back and forth due to the need for |
| 164 | a working copy. To work around this, you can get a Mutator handle that provides |
| 165 | direct access to the underlying object. As long as the Mutator is in scope, it |
| 166 | is invalid to access the underlying Persistent, but you'll be able to directly |
| 167 | modify the object in place. Once the Mutator goes out of scope, the Persistent |
| 168 | object'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_KEEP_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 | { |
Armando Montanez | 58f22dc | 2021-04-16 09:52:26 -0700 | [diff] [blame] | 191 | auto& mutable_crash_info = |
| 192 | persistent_crash_info.mutator(GetterAction::kReset); |
| 193 | vsnprintf(mutable_crash_info->reason, |
| 194 | sizeof(mutable_crash_info->reason), |
Armando Montanez | 9b085ce | 2021-03-19 15:12:25 -0700 | [diff] [blame] | 195 | fmt, |
| 196 | args); |
Armando Montanez | 58f22dc | 2021-04-16 09:52:26 -0700 | [diff] [blame] | 197 | mutable_crash_info->uptime_ms = system::GetUptimeMs(); |
| 198 | mutable_crash_info->boot_id = system::GetBootId(); |
Armando Montanez | 9b085ce | 2021-03-19 15:12:25 -0700 | [diff] [blame] | 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. |
Armando Montanez | 1b2a140 | 2021-04-16 10:53:48 -0700 | [diff] [blame] | 207 | persistent_crash_info.Invalidate(); |
Armando Montanez | 9b085ce | 2021-03-19 15:12:25 -0700 | [diff] [blame] | 208 | } |
| 209 | |
| 210 | // ... rest of main |
| 211 | } |
| 212 | |
Armando Montanez | 179aa8e | 2021-03-10 11:46:35 -0800 | [diff] [blame] | 213 | .. _module-pw_persistent_ram-persistent_buffer: |
| 214 | |
Armando Montanez | b90d9b2 | 2021-03-18 16:26:52 -0700 | [diff] [blame] | 215 | ------------------------------------ |
| 216 | pw::persistent_ram::PersistentBuffer |
| 217 | ------------------------------------ |
| 218 | The PersistentBuffer is a persistent storage container for variable-length |
| 219 | serialized data. Rather than allowing direct access to the underlying buffer for |
| 220 | random-access mutations, the PersistentBuffer is mutable through a |
| 221 | PersistentBufferWriter that implements the pw::stream::Writer interface. This |
| 222 | removes the potential for logical errors due to RAII or open()/close() semantics |
| 223 | as both the PersistentBuffer and PersistentBufferWriter can be used validly as |
| 224 | long as their access is serialized. |
| 225 | |
| 226 | Example |
| 227 | ------- |
| 228 | An example use case is emitting crash handler logs to a buffer for them to be |
| 229 | available after a the device reboots. Once the device reboots, the logs would be |
| 230 | emitted by the logging system. While this isn't always practical for plaintext |
| 231 | logs, 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 | |
Ewout van Bekkum | ac6a6ac | 2021-03-18 08:29:45 -0700 | [diff] [blame] | 263 | Size Report |
| 264 | ----------- |
| 265 | The following size report showcases the overhead for using Persistent. Note that |
| 266 | this is templating the Persistent only on a ``uint32_t``, ergo the cost without |
| 267 | pw_checksum's CRC16 is the approximate cost per type. |
| 268 | |
| 269 | .. include:: persistent_size |
| 270 | |
Ewout van Bekkum | 32dc5c5 | 2021-03-16 11:35:37 -0700 | [diff] [blame] | 271 | Compatibility |
| 272 | ------------- |
| 273 | * C++17 |
| 274 | |
| 275 | Dependencies |
| 276 | ------------ |
| 277 | * ``pw_checksum`` |