blob: 1890c05a533f4c1726e10fd164975ccd69c93a9a [file] [log] [blame]
// Copyright 2020 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 <cstddef>
#include <cstdint>
#include <initializer_list>
#include <span>
#include "pw_assert/assert.h"
#include "pw_kvs/alignment.h"
#include "pw_polyfill/standard.h"
#include "pw_status/status.h"
#include "pw_status/status_with_size.h"
#if PW_CXX_STANDARD_IS_SUPPORTED(17) // Requires C++17 for pw::Result
#include "pw_stream/seek.h"
#include "pw_stream/stream.h"
#endif // PW_CXX_STANDARD_IS_SUPPORTED(17)
namespace pw {
namespace kvs {
enum class PartitionPermission : bool {
kReadOnly,
kReadAndWrite,
};
class FlashMemory {
public:
// The flash address is in the range of: 0 to FlashSize.
typedef uint32_t Address;
// TODO(pwbug/246): This can be constexpr when tokenized asserts are fixed.
FlashMemory(size_t sector_size,
size_t sector_count,
size_t alignment,
uint32_t start_address = 0,
uint32_t sector_start = 0,
std::byte erased_memory_content = std::byte(0xFF))
: sector_size_(sector_size),
flash_sector_count_(sector_count),
alignment_(alignment),
start_address_(start_address),
start_sector_(sector_start),
erased_memory_content_(erased_memory_content) {
PW_ASSERT(alignment_ != 0u);
}
virtual ~FlashMemory() = default;
virtual Status Enable() = 0;
virtual Status Disable() = 0;
virtual bool IsEnabled() const = 0;
virtual Status SelfTest() { return Status::Unimplemented(); }
// Erase num_sectors starting at a given address. Blocking call.
// Address should be on a sector boundary. Returns:
//
// OK - success
// DEADLINE_EXCEEDED - timeout
// INVALID_ARGUMENT - address is not sector-aligned
// OUT_OF_RANGE - erases past the end of the memory
virtual Status Erase(Address flash_address, size_t num_sectors) = 0;
// Reads bytes from flash into buffer. Blocking call. Returns:
//
// OK - success
// DEADLINE_EXCEEDED - timeout
// OUT_OF_RANGE - write does not fit in the flash memory
virtual StatusWithSize Read(Address address, std::span<std::byte> output) = 0;
StatusWithSize Read(Address address, void* buffer, size_t len) {
return Read(address,
std::span<std::byte>(static_cast<std::byte*>(buffer), len));
}
// Writes bytes to flash. Blocking call. Returns:
//
// OK - success
// DEADLINE_EXCEEDED - timeout
// INVALID_ARGUMENT - address or data size are not aligned
// OUT_OF_RANGE - write does not fit in the memory
virtual StatusWithSize Write(Address destination_flash_address,
std::span<const std::byte> data) = 0;
StatusWithSize Write(Address destination_flash_address,
const void* data,
size_t len) {
return Write(
destination_flash_address,
std::span<const std::byte>(static_cast<const std::byte*>(data), len));
}
// Convert an Address to an MCU pointer, this can be used for memory
// mapped reads. Return NULL if the memory is not memory mapped.
virtual std::byte* FlashAddressToMcuAddress(Address) const { return nullptr; }
// start_sector() is useful for FlashMemory instances where the
// sector start is not 0. (ex.: cases where there are portions of flash
// that should be handled independently).
constexpr uint32_t start_sector() const { return start_sector_; }
constexpr size_t sector_size_bytes() const { return sector_size_; }
constexpr size_t sector_count() const { return flash_sector_count_; }
constexpr size_t alignment_bytes() const { return alignment_; }
constexpr size_t size_bytes() const {
return sector_size_ * flash_sector_count_;
}
// Address of the start of flash (the address of sector 0)
constexpr uint32_t start_address() const { return start_address_; }
constexpr std::byte erased_memory_content() const {
return erased_memory_content_;
}
private:
const uint32_t sector_size_;
const uint32_t flash_sector_count_;
const uint32_t alignment_;
const uint32_t start_address_;
const uint32_t start_sector_;
const std::byte erased_memory_content_;
};
class FlashPartition {
public:
// The flash address is in the range of: 0 to PartitionSize.
using Address = uint32_t;
#if PW_CXX_STANDARD_IS_SUPPORTED(17) // Requires C++17 for pw::Result
class Writer final : public stream::NonSeekableWriter {
public:
constexpr Writer(kvs::FlashPartition& partition)
: partition_(partition), position_(0) {}
private:
Status DoWrite(ConstByteSpan data) override;
size_t DoTell() override { return position_; }
size_t ConservativeLimit(LimitType type) const override {
return type == LimitType::kWrite ? partition_.size_bytes() - position_
: 0;
}
FlashPartition& partition_;
size_t position_;
};
class Reader final : public stream::SeekableReader {
public:
constexpr Reader(kvs::FlashPartition& partition)
: partition_(partition), position_(0) {}
Reader(const Reader&) = delete;
Reader& operator=(const Reader&) = delete;
private:
StatusWithSize DoRead(ByteSpan data) override;
size_t DoTell() override { return position_; }
Status DoSeek(ptrdiff_t offset, Whence origin) override {
return CalculateSeek(offset, origin, partition_.size_bytes(), position_);
}
size_t ConservativeLimit(LimitType type) const override {
return type == LimitType::kRead ? partition_.size_bytes() - position_ : 0;
}
FlashPartition& partition_;
size_t position_;
};
#endif // PW_CXX_STANDARD_IS_SUPPORTED(17)
// Implement Output for the Write method.
class Output final : public pw::Output {
public:
constexpr Output(FlashPartition& flash, FlashPartition::Address address)
: flash_(flash), address_(address) {}
private:
StatusWithSize DoWrite(std::span<const std::byte> data) override;
FlashPartition& flash_;
FlashPartition::Address address_;
};
// Implement Input for the Read method.
class Input final : public pw::Input {
public:
constexpr Input(FlashPartition& flash, FlashPartition::Address address)
: flash_(flash), address_(address) {}
private:
StatusWithSize DoRead(std::span<std::byte> data) override;
FlashPartition& flash_;
FlashPartition::Address address_;
};
FlashPartition(
FlashMemory* flash,
uint32_t start_sector_index,
uint32_t sector_count,
uint32_t alignment_bytes = 0, // Defaults to flash alignment
PartitionPermission permission = PartitionPermission::kReadAndWrite);
// Creates a FlashPartition that uses the entire flash with its alignment.
FlashPartition(FlashMemory* flash)
: FlashPartition(
flash, 0, flash->sector_count(), flash->alignment_bytes()) {}
FlashPartition(FlashPartition&&) = default;
FlashPartition(const FlashPartition&) = delete;
FlashPartition& operator=(const FlashPartition&) = delete;
virtual ~FlashPartition() = default;
// Performs any required partition or flash-level initialization.
virtual Status Init() { return OkStatus(); }
// Erase num_sectors starting at a given address. Blocking call.
// Address must be on a sector boundary. Returns:
//
// OK - success.
// TIMEOUT - on timeout.
// INVALID_ARGUMENT - address or sector count is invalid.
// PERMISSION_DENIED - partition is read only.
// UNKNOWN - HAL error
virtual Status Erase(Address address, size_t num_sectors);
Status Erase() { return Erase(0, this->sector_count()); }
// Reads bytes from flash into buffer. Blocking call. Returns:
//
// OK - success.
// TIMEOUT - on timeout.
// INVALID_ARGUMENT - address or length is invalid.
// UNKNOWN - HAL error
virtual StatusWithSize Read(Address address, std::span<std::byte> output);
StatusWithSize Read(Address address, size_t length, void* output) {
return Read(address,
std::span<std::byte>(static_cast<std::byte*>(output), length));
}
// Writes bytes to flash. Address and data.size_bytes() must both be a
// multiple of alignment_bytes(). Blocking call. Returns:
//
// OK - success.
// TIMEOUT - on timeout.
// INVALID_ARGUMENT - address or length is invalid.
// PERMISSION_DENIED - partition is read only.
// UNKNOWN - HAL error
virtual StatusWithSize Write(Address address,
std::span<const std::byte> data);
// Check to see if chunk of flash partition is erased. Address and len need to
// be aligned with FlashMemory. Returns:
//
// OK - success.
// TIMEOUT - on timeout.
// INVALID_ARGUMENT - address or length is invalid.
// UNKNOWN - HAL error
// TODO: Result<bool>
virtual Status IsRegionErased(Address source_flash_address,
size_t length,
bool* is_erased);
// Check if the entire partition is erased.
// Returns: same as IsRegionErased().
Status IsErased(bool* is_erased) {
return IsRegionErased(0, this->size_bytes(), is_erased);
}
// Checks to see if the data appears to be erased. No reads or writes occur;
// the FlashPartition simply compares the data to
// flash_.erased_memory_content().
bool AppearsErased(std::span<const std::byte> data) const;
// Overridden by derived classes. The reported sector size is space available
// to users of FlashPartition. It accounts for space reserved in the sector
// for FlashPartition to store metadata.
virtual size_t sector_size_bytes() const {
return flash_.sector_size_bytes();
}
size_t size_bytes() const { return sector_count() * sector_size_bytes(); }
// Alignment required for write address and write size.
size_t alignment_bytes() const { return alignment_bytes_; }
size_t sector_count() const { return sector_count_; }
// Convert a FlashMemory::Address to an MCU pointer, this can be used for
// memory mapped reads. Return NULL if the memory is not memory mapped.
std::byte* PartitionAddressToMcuAddress(Address address) const {
return flash_.FlashAddressToMcuAddress(PartitionToFlashAddress(address));
}
// Converts an address from the partition address space to the flash address
// space. If the partition reserves additional space in the sector, the flash
// address space may not be contiguous, and this conversion accounts for that.
virtual FlashMemory::Address PartitionToFlashAddress(Address address) const {
return flash_.start_address() +
(start_sector_index_ - flash_.start_sector()) * sector_size_bytes() +
address;
}
bool writable() const {
return permission_ == PartitionPermission::kReadAndWrite;
}
constexpr std::byte erased_memory_content() const {
return flash_.erased_memory_content();
}
uint32_t start_sector_index() const { return start_sector_index_; }
protected:
Status CheckBounds(Address address, size_t len) const;
FlashMemory& flash() const { return flash_; }
private:
FlashMemory& flash_;
const uint32_t start_sector_index_;
const uint32_t sector_count_;
const uint32_t alignment_bytes_;
const PartitionPermission permission_;
};
} // namespace kvs
} // namespace pw