• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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