• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, 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_PLACE_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
50      #include "pw_assert/check.h"
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-----------------------------------
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
78RAM 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
80after 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
120Example: Storing an integer
121---------------------------
122A 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_PLACE_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
161Example: 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_PLACE_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      {
191        auto& mutable_crash_info =
192            persistent_crash_info.mutator(GetterAction::kReset);
193        vsnprintf(mutable_crash_info->reason,
194                  sizeof(mutable_crash_info->reason),
195                  fmt,
196                  args);
197        mutable_crash_info->uptime_ms = system::GetUptimeMs();
198        mutable_crash_info->boot_id = system::GetBootId();
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.
207        persistent_crash_info.Invalidate();
208      }
209
210      // ... rest of main
211    }
212
213.. _module-pw_persistent_ram-persistent_buffer:
214
215------------------------------------
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
263Size 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
271Compatibility
272-------------
273* C++17
274
275Dependencies
276------------
277* ``pw_checksum``
278