blob: 404f5004083bbb7d8e98563554ea5cc635403983 [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/assert.h"
#include "pw_checksum/crc16_ccitt.h"
#include "pw_preprocessor/compiler.h"
namespace pw::persistent_ram {
// Behavior to use when attempting to get a handle to the underlying data stored
// in persistent memory.
enum class GetterAction {
// Default-construct the object before returning a handle.
kReset,
// Assert that the object is valid before returning a handle.
kAssertValid,
};
// The Persistent class intentionally uses uninitialized memory, which triggers
// compiler warnings. Disable those warnings for this file.
PW_MODIFY_DIAGNOSTICS_PUSH();
PW_MODIFY_DIAGNOSTIC(ignored, "-Wuninitialized");
PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wmaybe-uninitialized");
// 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:
// This object provides mutable access to the underlying object of a
// Persistent<T>.
//
// WARNING: This object must remain in scope for any modifications of the
// Underlying object. If the object is modified after the Mutator goes out
// of scope, the CRC will not be updated to reflect changes, invalidating the
// contents of the Persistent<T>!
//
// WARNING: Persistent<T>::has_value() will return false if there are
// in-flight modifications by a Mutator that have not yet been flushed.
class Mutator {
public:
explicit constexpr Mutator(Persistent<T>& persistent)
: persistent_(persistent) {}
~Mutator() { persistent_.crc_ = persistent_.CalculateCrc(); }
Mutator(const Mutator&) = delete; // Copy constructor is disabled.
T* operator->() { return const_cast<T*>(&persistent_.contents_); }
// Be careful when sharing a reference or pointer to the underlying object.
// Once the Mutator goes out of scope, any changes to the object will
// invalidate the checksum. Avoid directly using the underlying object
// unless you need to pass it to a function.
T& value() { return persistent_.contents_; }
T& operator*() { return *const_cast<T*>(&persistent_.contents_); }
private:
Persistent<T>& persistent_;
};
// 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_ = CalculateCrc();
return const_cast<T&>(contents_);
}
// Assignment operator.
template <typename U = T>
Persistent& operator=(U&& value) {
contents_ = std::move(value);
crc_ = CalculateCrc();
return *this;
}
// Destroys any contained value.
void Invalidate() {
// The trivial destructor is skipped as it's trivial.
std::memset(const_cast<T*>(&contents_), 0, sizeof(contents_));
crc_ = 0;
}
// This is deprecated, use Invalidate() instead.
[[deprecated]] void reset() { Invalidate(); }
// Returns true if a value is held by the Persistent.
bool has_value() const {
return crc_ == CalculateCrc(); // 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_);
}
// Get a mutable handle to the underlying data.
//
// Args:
// action: Whether to default-construct the underlying value before
// providing a mutator, or to assert that the object is valid
// without modifying the underlying data.
// Precondition: has_value() must be true.
Mutator mutator(GetterAction action = GetterAction::kAssertValid) {
if (action == GetterAction::kReset) {
emplace();
} else {
PW_ASSERT(has_value());
}
return Mutator(*this);
}
private:
friend class Mutator;
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.");
uint16_t CalculateCrc() const {
return checksum::Crc16Ccitt::Calculate(
std::as_bytes(std::span(const_cast<const T*>(&contents_), 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_;
};
};
PW_MODIFY_DIAGNOSTICS_POP();
} // namespace pw::persistent_ram