blob: 0bdb873e9a17447e4a21bee6115b0584f8f7f4be [file] [log] [blame]
.. _module-pw_persistent_ram:
=================
pw_persistent_ram
=================
The ``pw_persistent_ram`` module contains utilities and containers for using
persistent RAM. By persistent RAM we are referring to memory which is not
initialized across reboots by the hardware nor bootloader(s). This memory may
decay or bit rot between reboots including brownouts, ergo integrity checking is
highly recommended.
.. Note::
This is something that not all architectures and applications built on them
support and requires hardware in the loop testing to verify it works as
intended.
.. Warning::
Do not treat the current containers provided in this module as stable storage
primitives. We are still evaluating lighterweight checksums from a code size
point of view. In other words, future updates to this module may result in a
loss of persistent data across software updates.
------------------------
Persistent RAM Placement
------------------------
Persistent RAM is typically provided through specially carved out linker script
sections and/or memory ranges which are located in such a way that any
bootloaders and the application boot code do not clobber it.
1. If persistent linker sections are provided, we recommend using our section
placement macro. For example imagine the persistent section name is called
`.noinit`, then you could instantiate an object as such:
.. code-block:: cpp
#include "pw_persistent_ram/persistent.h"
#include "pw_preprocessor/compiler.h"
using pw::persistent_ram::Persistent;
PW_PLACE_IN_SECTION(".noinit") Persistent<bool> persistent_bool;
2. If persistent memory ranges are provided, we recommend using a struct to wrap
the different persisted objects. This then could be checked to fit in the
provided memory range size, for example by asserting against variables
provided through a linker script.
.. code-block:: cpp
#include "pw_assert/check.h"
#include "pw_persistent_ram/persistent.h"
// Provided for example through a linker script.
extern "C" uint8_t __noinit_begin;
extern "C" uint8_t __noinit_end;
struct PersistentData {
Persistent<bool> persistent_bool;
};
PersistentData& persistent_data =
*reinterpret_cast<NoinitData*>(&__noinit_begin);
void CheckPersistentDataSize() {
PW_DCHECK_UINT_LE(sizeof(PersistentData),
__noinit_end - __noinit_begin,
"PersistentData overflowed the noinit memory range");
}
-----------------------------------
Persistent RAM Lifecycle Management
-----------------------------------
In order for persistent RAM containers to be as useful as possible, any
invalidation of persistent RAM and the containers therein should be executed
before the global static C++ constructors, but after the BSS and data sections
are initialized in RAM.
The preferred way to clear Persistent RAM is to simply zero entire persistent
RAM sections and/or memory regions. Pigweed's persistent containers have picked
integrity checks which work with zeroed memory, meaning they do not hold a value
after zeroing. Alternatively containers can be individually cleared.
The boot sequence itself is tightly coupled to the number of persistent sections
and/or memory regions which exist in the final image, ergo this is something
which Pigweed cannot provide to the user directly. However, we do recommend
following some guidelines:
1. Do not instantiate regular types/objects in persistent RAM, ensure integrity
checking is always used! This is a major risk with this technique and can
lead to unexpected memory corruption.
2. Always instantiate persistent containers outside of the objects which depend
on them and use dependency injection. This permits unit testing and avoids
placement accidents of persistents and/or their users.
3. Always erase persistent RAM data after software updates unless the
persistent storage containers are explicitly stored at fixed address and
with a fixed layout. This prevents use of swapped objects or their members
where the same integrity checks are used.
4. Consider zeroing persistent RAM to recover from crashes which may be induced
by persistent RAM usage, for example by checking the reboot/crash reason.
5. Consider zeroing persistent RAM on cold boots to always start from a
consistent state if persistence is only desired across warm reboots. This can
create determinism from cold boots when using for example DRAM.
6. Consider an explicit persistent clear request which can be set before a warm
reboot as a signal to zero all persistent RAM on the next boot to emulate
persistent memory loss in a threadsafe manner.
---------------------------------
pw::persistent_ram::Persistent<T>
---------------------------------
The Persistent is a simple container for holding its templated value ``T`` with
CRC16 integrity checking. Note that a Persistent will be lost if a write/set
operation is interrupted or otherwise not completed, as it is not double
buffered.
The default constructor does nothing, meaning it will result in either invalid
state initially or a valid persisted value from a previous session.
The destructor does nothing, ergo it is okay if it is not executed during
shutdown.
Example: Storing an integer
---------------------------
A common use case of persistent data is to track boot counts, or effectively
how often the device has rebooted. This can be useful for monitoring how many
times the device rebooted and/or crashed. This can be easily accomplished using
the Persistent container.
.. code-block:: cpp
#include "pw_persistent_ram/persistent.h"
#include "pw_preprocessor/compiler.h"
using pw::persistent_ram::Persistent;
class BootCount {
public:
explicit BootCount(Persistent<uint16_t>& persistent_boot_count)
: persistent_(persistent_boot_count) {
if (!persistent_.has_value()) {
persistent_ = 0;
} else {
persistent_ = persistent_.value() + 1;
}
boot_count_ = persistent_.value();
}
uint16_t GetBootCount() { return boot_count_; }
private:
Persistent<uint16_t>& persistent_;
uint16_t boot_count_;
};
PW_PLACE_IN_SECTION(".noinit") Persistent<uint16_t> persistent_boot_count;
BootCount boot_count(persistent_boot_count);
int main() {
const uint16_t boot_count = boot_count.GetBootCount();
// ... rest of main
}
Example: Storing larger objects
-------------------------------
Larger objects may be inefficient to copy back and forth due to the need for
a working copy. To work around this, you can get a Mutator handle that provides
direct access to the underlying object. As long as the Mutator is in scope, it
is invalid to access the underlying Persistent, but you'll be able to directly
modify the object in place. Once the Mutator goes out of scope, the Persistent
object's checksum is updated to reflect the changes.
.. code-block:: cpp
#include "pw_persistent_ram/persistent.h"
#include "pw_preprocessor/compiler.h"
using pw::persistent_ram::Persistent;
contexpr size_t kMaxReasonLength = 256;
struct LastCrashInfo {
uint32_t uptime_ms;
uint32_t boot_id;
char reason[kMaxReasonLength];
}
PW_PLACE_IN_SECTION(".noinit") Persistent<LastBootInfo> persistent_crash_info;
void HandleCrash(const char* fmt, va_list args) {
// Once this scope ends, we know the persistent object has been updated
// to reflect changes.
{
auto& mutable_crash_info =
persistent_crash_info.mutator(GetterAction::kReset);
vsnprintf(mutable_crash_info->reason,
sizeof(mutable_crash_info->reason),
fmt,
args);
mutable_crash_info->uptime_ms = system::GetUptimeMs();
mutable_crash_info->boot_id = system::GetBootId();
}
// ...
}
int main() {
if (persistent_crash_info.has_value()) {
LogLastCrashInfo(persistent_crash_info.value());
// Clear crash info once it has been dumped.
persistent_crash_info.Invalidate();
}
// ... rest of main
}
.. _module-pw_persistent_ram-persistent_buffer:
------------------------------------
pw::persistent_ram::PersistentBuffer
------------------------------------
The PersistentBuffer is a persistent storage container for variable-length
serialized data. Rather than allowing direct access to the underlying buffer for
random-access mutations, the PersistentBuffer is mutable through a
PersistentBufferWriter that implements the pw::stream::Writer interface. This
removes the potential for logical errors due to RAII or open()/close() semantics
as both the PersistentBuffer and PersistentBufferWriter can be used validly as
long as their access is serialized.
Example
-------
An example use case is emitting crash handler logs to a buffer for them to be
available after a the device reboots. Once the device reboots, the logs would be
emitted by the logging system. While this isn't always practical for plaintext
logs, tokenized logs are small enough for this to be useful.
.. code-block:: cpp
#include "pw_persistent_ram/persistent_buffer.h"
#include "pw_preprocessor/compiler.h"
using pw::persistent_ram::PersistentBuffer;
using pw::persistent_ram::PersistentBuffer::PersistentBufferWriter;
PW_KEEP_IN_SECTION(".noinit") PersistentBuffer<2048> crash_logs;
void CheckForCrashLogs() {
if (crash_logs.has_value()) {
// A function that dumps sequentially serialized logs using pw_log.
DumpRawLogs(crash_logs.written_data());
crash_logs.clear();
}
}
void HandleCrash(CrashInfo* crash_info) {
PersistentBufferWriter crash_log_writer = crash_logs.GetWriter();
// Sets the pw::stream::Writer that pw_log should dump logs to.
crash_log_writer.clear();
SetLogSink(crash_log_writer);
// Handle crash, calling PW_LOG to log useful info.
}
int main() {
void CheckForCrashLogs();
// ... rest of main
}
Size Report
-----------
The following size report showcases the overhead for using Persistent. Note that
this is templating the Persistent only on a ``uint32_t``, ergo the cost without
pw_checksum's CRC16 is the approximate cost per type.
.. include:: persistent_size
Compatibility
-------------
* C++17
Dependencies
------------
* ``pw_checksum``