blob: 06f356b30582ab1d6115281d73083ca8741473d7 [file] [log] [blame]
// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once
#include <cstdint>
#include <cstring>
#include <span>
#include <type_traits>
#include <utility>
#include "pw_assert/light.h"
#include "pw_checksum/crc16_ccitt.h"
namespace pw::persistent_ram {
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wuninitialized"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuninitialized"
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
// A simple container for holding a value T with CRC16 integrity checking.
//
// A Persistent is simply a value T plus integrity checking for use in a
// persistent RAM section which is not initialized on boot.
//
// WARNING: Unlike a DoubleBufferedPersistent, a Persistent will be lost if a
// write/set operation is interrupted or otherwise not completed.
//
// TODO(pwbug/348): Consider a different integrity check implementation which
// does not use a 512B lookup table.
template <typename T>
class Persistent {
public:
// Constructor which does nothing, meaning it never sets the value.
constexpr Persistent() {}
Persistent(const Persistent&) = delete; // Copy constructor is disabled.
Persistent(Persistent&&) = delete; // Move constructor is disabled.
~Persistent() {} // The destructor does nothing.
// Construct the value in-place.
template <class... Args>
const T& emplace(Args&&... args) {
new (const_cast<T*>(&contents_)) T(std::forward<Args>(args)...);
crc_ = Crc(contents_);
return const_cast<T&>(contents_);
}
// Assignment operator.
template <typename U = T>
Persistent& operator=(U&& value) {
contents_ = std::move(value);
crc_ = Crc(contents_);
return *this;
}
// Destroys any contained value.
void reset() {
// The trivial destructor is skipped as it's trivial.
std::memset(const_cast<T*>(&contents_), 0, sizeof(contents_));
crc_ = 0;
}
// Returns true if a value is held by the Persistent.
bool has_value() const {
return crc_ == Crc(contents_); // There's a value if its CRC matches.
}
// Access the value.
//
// Precondition: has_value() must be true.
const T& value() const {
PW_ASSERT(has_value());
return const_cast<T&>(contents_);
}
private:
static_assert(std::is_trivially_copy_constructible<T>::value,
"If a Persistent persists across reboots, it is effectively "
"loaded through a trivial copy constructor.");
static_assert(std::is_trivially_destructible<T>::value,
"A Persistent's destructor does not invoke the value's "
"destructor, ergo only trivially destructible types are "
"supported.");
static uint16_t Crc(volatile const T& value) {
return checksum::Crc16Ccitt::Calculate(
std::as_bytes(std::span(const_cast<const T*>(&value), 1)));
}
// Use unions to denote that these members are never initialized by design and
// on purpose. Volatile is used to ensure that the compiler cannot optimize
// out operations where it seems like there is no further usage of a
// Persistent as this may be on the next boot.
union {
volatile T contents_;
};
union {
volatile uint16_t crc_;
};
};
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
} // namespace pw::persistent_ram