pw_persistent_ram#

Pigweed AI summary: The pw_persistent_ram module provides utilities and containers for using persistent RAM, which is memory that is not initialized across reboots. It is recommended to perform integrity checking on persistent RAM due to the possibility of decay or bit rot between reboots. The module also provides guidelines for persistent RAM placement and lifecycle management. It includes the Persistent container for holding values with CRC16 integrity checking, as well as the PersistentBuffer container for variable-length serialized data. The module provides examples of storing an integer and

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#

Pigweed AI summary: This section discusses the placement of persistent RAM in embedded systems. It suggests using linker script sections or memory ranges to ensure that bootloaders and application boot code do not overwrite the persistent RAM. The section provides examples of how to instantiate objects using the recommended section placement macro and how to wrap persisted objects in a struct to check if they fit within the provided memory range size. The code also includes assertions to prevent overflow of the persistent data.

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:

    #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.

    #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#

Pigweed AI summary: This article discusses the management of persistent RAM containers, emphasizing the importance of clearing invalidation before global static C++ constructors. The preferred method of clearing persistent RAM is to zero entire sections and/or memory regions, and Pigweed's persistent containers have integrity checks that work with zeroed memory. The article provides guidelines for managing persistent RAM, including not instantiating regular types/objects in persistent RAM, erasing persistent RAM data after software updates, and considering zeroing persistent RAM on cold boots for consistency. Additionally

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>#

Pigweed AI summary: The pw::persistent_ram::Persistent<T> is a container that holds a templated value with CRC16 integrity checking. It will be lost if a write/set operation is interrupted or not completed. The default constructor does nothing, and the destructor does nothing. An example use case is tracking boot counts, which can be easily accomplished using the Persistent container. Larger objects may be inefficient to copy back and forth, but a Mutator handle can provide direct access to the underlying object for modification. Once the Mut

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#

Pigweed AI summary: This article discusses the use of persistent data to track boot counts, which can be useful for monitoring device reboots and crashes. The article provides an example of how to accomplish this using the Persistent container in C++. The code includes a class called BootCount, which uses a Persistent object to store and retrieve the boot count value. The article also includes a code snippet that demonstrates how to use the BootCount class in a main function.

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.

#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#

Pigweed AI summary: This section discusses how to efficiently store larger objects by using a Mutator handle that provides direct access to the underlying object. This allows for direct modification of the object in place, without the need for inefficient copying back and forth. The example code provided demonstrates how to use a Mutator handle to update a persistent object reflecting the last crash information. Once the Mutator goes out of scope, the Persistent object's checksum is updated to reflect the changes.

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.

#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
}

pw::persistent_ram::PersistentBuffer#

Pigweed AI summary: The PersistentBuffer is a storage container for serialized data that is mutable through a PersistentBufferWriter, which implements the pw::stream::Writer interface. This removes the potential for logical errors due to RAII or open()/close() semantics. An example use case is emitting crash handler logs to a buffer for them to be available after a device reboots. The size report showcases the overhead for using Persistent, and the compatibility is C++17 with a dependency on pw_checksum.

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#

Pigweed AI summary: This example demonstrates how crash handler logs can be emitted to a buffer and made available after a device reboots. The logs are emitted by the logging system once the device reboots. Tokenized logs are small enough to make this practical. The code includes the use of a persistent buffer and a function to dump serialized logs using pw_log. The HandleCrash function is used to handle crashes and log useful information using PW_LOG.

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.

#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#

Pigweed AI summary: This section provides a size report for using Persistent, with and without pw_checksum's CRC16, templated only on uint32_t. The report includes a table showing the overhead for using Persistent, with the delta in size for each segment. The report shows that using Persistent with pw_checksum's CRC16 adds 648 bytes, while using it without adds only 72 bytes.

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.

Label

Segment

Delta

Persistent including pw_checksum’s CRC16

FLASH

+4

quorem

+88

main

NEW

+512

pw::checksum::(anonymous namespace)::kCrc16CcittTable

NEW

+44

pw_checksum_Crc16Ccitt

+648

Persistent without pw_checksum’s CRC16

FLASH

+72

main

+72

Compatibility#

Pigweed AI summary: Compatibility: C++17. This section lists the compatibility of the program with C++17.

  • C++17

Dependencies#

Pigweed AI summary: The "Dependencies" section lists only one item, which is the "pw_checksum" literal.

  • pw_checksum