blob: 4e659c2b94f44c67b389234db354daa42e787932 [file] [log] [blame]
Ewout van Bekkum32dc5c52021-03-16 11:35:37 -07001.. _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_KEEP_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
Wyatt Heplerf298de42021-03-19 15:06:36 -070050 #include "pw_assert/check.h"
Ewout van Bekkum32dc5c52021-03-16 11:35:37 -070051 #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
Shiva Rajagopal9e516562021-05-11 17:04:15 -070078RAM 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
Ewout van Bekkum32dc5c52021-03-16 11:35:37 -070080after 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
Armando Montanez9b085ce2021-03-19 15:12:25 -0700120Example: Storing an integer
121---------------------------
Ewout van Bekkum32dc5c52021-03-16 11:35:37 -0700122A 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_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 Montanez9b085ce2021-03-19 15:12:25 -0700161Example: 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_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 Montanez58f22dc2021-04-16 09:52:26 -0700191 auto& mutable_crash_info =
192 persistent_crash_info.mutator(GetterAction::kReset);
193 vsnprintf(mutable_crash_info->reason,
194 sizeof(mutable_crash_info->reason),
Armando Montanez9b085ce2021-03-19 15:12:25 -0700195 fmt,
196 args);
Armando Montanez58f22dc2021-04-16 09:52:26 -0700197 mutable_crash_info->uptime_ms = system::GetUptimeMs();
198 mutable_crash_info->boot_id = system::GetBootId();
Armando Montanez9b085ce2021-03-19 15:12:25 -0700199 }
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 Montanez1b2a1402021-04-16 10:53:48 -0700207 persistent_crash_info.Invalidate();
Armando Montanez9b085ce2021-03-19 15:12:25 -0700208 }
209
210 // ... rest of main
211 }
212
Armando Montanez179aa8e2021-03-10 11:46:35 -0800213.. _module-pw_persistent_ram-persistent_buffer:
214
Armando Montanezb90d9b22021-03-18 16:26:52 -0700215------------------------------------
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
Ewout van Bekkumac6a6ac2021-03-18 08:29:45 -0700263Size 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
Ewout van Bekkum32dc5c52021-03-16 11:35:37 -0700271Compatibility
272-------------
273* C++17
274
275Dependencies
276------------
277* ``pw_checksum``