xref: /aosp_15_r20/external/pigweed/pw_persistent_ram/docs.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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