pw_kvs: Initial commit of key value store module
This commit does not build or pass presubmit checks.
Change-Id: I3d4dd393ede1c778888c3cd8be9f12dfbf92fb88
diff --git a/pw_kvs/public/pw_kvs/flash_memory.h b/pw_kvs/public/pw_kvs/flash_memory.h
new file mode 100644
index 0000000..707f128
--- /dev/null
+++ b/pw_kvs/public/pw_kvs/flash_memory.h
@@ -0,0 +1,377 @@
+// 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 <algorithm>
+
+#include "pw_kvs/assert.h"
+#include "pw_kvs/logging.h"
+#include "pw_kvs/peripherals/partition_table_entry.h"
+#include "pw_kvs/status.h"
+#include "pw_kvs/status_macros.h"
+
+namespace pw {
+class FlashMemory {
+ public:
+ // The flash address is in the range of: 0 to FlashSize.
+ typedef uint32_t Address;
+ constexpr FlashMemory(uint32_t sector_size,
+ uint32_t sector_count,
+ uint8_t alignment,
+ uint32_t start_address = 0,
+ uint32_t sector_start = 0,
+ uint8_t erased_memory_content = 0xFF)
+ : sector_size_(sector_size),
+ flash_sector_count_(sector_count),
+ alignment_(alignment),
+ start_address_(start_address),
+ sector_start_(sector_start),
+ erased_memory_content_(erased_memory_content) {}
+
+ 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, on success.
+ // TIMEOUT, on timeout.
+ // INVALID_ARGUMENT, if address or sector count is invalid.
+ // UNKNOWN, on HAL error
+ virtual Status Erase(Address flash_address, uint32_t num_sectors) = 0;
+
+ // Reads bytes from flash into buffer. Blocking call.
+ // Returns: OK, on success.
+ // TIMEOUT, on timeout.
+ // INVALID_ARGUMENT, if address or length is invalid.
+ // UNKNOWN, on HAL error
+ virtual Status Read(uint8_t* destination_ram_address,
+ Address source_flash_address,
+ uint32_t len) = 0;
+
+ // Writes bytes to flash. Blocking call.
+ // Returns: OK, on success.
+ // TIMEOUT, on timeout.
+ // INVALID_ARGUMENT, if address or length is invalid.
+ // UNKNOWN, on HAL error
+ virtual Status Write(Address destination_flash_address,
+ const uint8_t* source_ram_address,
+ uint32_t len) = 0;
+
+ // 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 uint8_t* FlashAddressToMcuAddress(Address address) const {
+ return nullptr;
+ }
+
+ // GetStartSector() 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 GetStartSector() const { return sector_start_; }
+ constexpr uint32_t GetSectorSizeBytes() const { return sector_size_; }
+ constexpr uint32_t GetSectorCount() const { return flash_sector_count_; }
+ constexpr uint8_t GetAlignmentBytes() const { return alignment_; }
+ constexpr uint32_t GetSizeBytes() const {
+ return sector_size_ * flash_sector_count_;
+ }
+ // Address of the start of flash (the address of sector 0)
+ constexpr uint32_t GetStartAddress() const { return start_address_; }
+ constexpr uint8_t GetErasedMemoryContent() const {
+ return erased_memory_content_;
+ }
+
+ private:
+ const uint32_t sector_size_;
+ const uint32_t flash_sector_count_;
+ const uint8_t alignment_;
+ const uint32_t start_address_;
+ const uint32_t sector_start_;
+ const uint8_t erased_memory_content_;
+};
+
+// Exposes a sub-sector sized region of flash memory that cannot be erased.
+// It can be thought of as one pseudo-sector that is sized exactly as provided.
+//
+// TODO(b/117553777): This makes a little more sense as a SubSectorPartition,
+// but PartitionTableEntry currently assumes all partitions fill entire sectors.
+// Revisit when PartitionTable is refactored.
+class FlashMemorySubSector : public FlashMemory {
+ public:
+ constexpr FlashMemorySubSector(FlashMemory* flash,
+ uint32_t start_address,
+ uint32_t size)
+ : FlashMemory(size,
+ 1, // Round up to "1" sector.
+ flash->GetAlignmentBytes(),
+ start_address,
+ // Calculate the sector for this start address.
+ flash->GetStartSector() +
+ ((start_address - flash->GetStartAddress()) /
+ flash->GetSectorSizeBytes())),
+ flash_(*CHECK_NOTNULL(flash)),
+ base_offset_(start_address - flash->GetStartAddress()) {
+ // Make sure we're not specifying a region of flash larger than
+ // that which the underlying FlashMemory supports.
+ CHECK(start_address >= flash->GetStartAddress());
+ CHECK(size <= flash->GetSectorSizeBytes());
+ CHECK(start_address + size <=
+ flash->GetStartAddress() + flash->GetSizeBytes());
+ CHECK_EQ(0, start_address % flash->GetAlignmentBytes());
+ CHECK_EQ(0, size % flash->GetAlignmentBytes());
+ }
+
+ Status Enable() override { return flash_.Enable(); }
+ Status Disable() override { return flash_.Disable(); }
+ bool IsEnabled() const override { return flash_.IsEnabled(); }
+ Status SelfTest() override { return flash_.SelfTest(); }
+
+ Status Erase(Address flash_address, uint32_t num_sectors) override {
+ return Status::UNIMPLEMENTED;
+ }
+
+ Status Read(uint8_t* destination_ram_address,
+ Address source_flash_address,
+ uint32_t len) override {
+ return flash_.Read(destination_ram_address, source_flash_address, len);
+ }
+
+ Status Write(Address destination_flash_address,
+ const uint8_t* source_ram_address,
+ uint32_t len) override {
+ return flash_.Write(destination_flash_address, source_ram_address, len);
+ }
+
+ uint8_t* FlashAddressToMcuAddress(Address address) const override {
+ return flash_.FlashAddressToMcuAddress(base_offset_ + address);
+ }
+
+ private:
+ FlashMemory& flash_;
+ // Value to add to addresses to get to the underlying flash_ address.
+ const Address base_offset_;
+};
+
+class FlashPartition {
+ public:
+ // The flash address is in the range of: 0 to PartitionSize.
+ typedef uint32_t Address;
+
+ constexpr FlashPartition(
+ FlashMemory* flash,
+ uint32_t start_sector_index,
+ uint32_t sector_count,
+ PartitionPermission permission = PartitionPermission::kReadAndWrite)
+ : flash_(*flash),
+ start_sector_index_(start_sector_index),
+ sector_count_(sector_count),
+ permission_(permission) {}
+
+ constexpr FlashPartition(FlashMemory* flash, PartitionTableEntry entry)
+ : flash_(*flash),
+ start_sector_index_(entry.partition_start_sector_index),
+ sector_count_(entry.partition_end_sector_index -
+ entry.partition_start_sector_index + 1),
+ permission_(entry.partition_permission) {}
+
+ // Erase num_sectors starting at a given address. Blocking call.
+ // Address should be on a sector boundary.
+ // Returns: OK, on success.
+ // TIMEOUT, on timeout.
+ // INVALID_ARGUMENT, if address or sector count is invalid.
+ // PERMISSION_DENIED, if partition is read only.
+ // UNKNOWN, on HAL error
+ virtual Status Erase(Address address, uint32_t num_sectors) {
+ RETURN_STATUS_IF(permission_ == PartitionPermission::kReadOnly,
+ Status::PERMISSION_DENIED);
+ RETURN_IF_ERROR(CheckBounds(address, num_sectors * GetSectorSizeBytes()));
+ return flash_.Erase(PartitionToFlashAddress(address), num_sectors);
+ }
+
+ // Reads bytes from flash into buffer. Blocking call.
+ // Returns: OK, on success.
+ // TIMEOUT, on timeout.
+ // INVALID_ARGUMENT, if address or length is invalid.
+ // UNKNOWN, on HAL error
+ virtual Status Read(uint8_t* destination_ram_address,
+ Address source_flash_address,
+ uint32_t len) {
+ RETURN_IF_ERROR(CheckBounds(source_flash_address, len));
+ return flash_.Read(destination_ram_address,
+ PartitionToFlashAddress(source_flash_address),
+ len);
+ }
+
+ // Writes bytes to flash. Blocking call.
+ // Returns: OK, on success.
+ // TIMEOUT, on timeout.
+ // INVALID_ARGUMENT, if address or length is invalid.
+ // PERMISSION_DENIED, if partition is read only.
+ // UNKNOWN, on HAL error
+ virtual Status Write(Address destination_flash_address,
+ const uint8_t* source_ram_address,
+ uint32_t len) {
+ RETURN_STATUS_IF(permission_ == PartitionPermission::kReadOnly,
+ Status::PERMISSION_DENIED);
+ RETURN_IF_ERROR(CheckBounds(destination_flash_address, len));
+ return flash_.Write(PartitionToFlashAddress(destination_flash_address),
+ source_ram_address,
+ len);
+ }
+
+ // Check to see if chunk of flash memory is erased. Address and len need to
+ // be aligned with FlashMemory.
+ // Returns: OK, on success.
+ // TIMEOUT, on timeout.
+ // INVALID_ARGUMENT, if address or length is invalid.
+ // UNKNOWN, on HAL error
+ virtual Status IsChunkErased(Address source_flash_address,
+ uint32_t len,
+ bool* is_erased) {
+ // Max alignment is artifical to keep the stack usage low for this
+ // function. Using 16 because it's the alignment of encrypted flash.
+ const uint8_t kMaxAlignment = 16;
+ // Relying on Read() to check address and len arguments.
+ RETURN_STATUS_IF(!is_erased, Status::INVALID_ARGUMENT);
+ uint8_t alignment = GetAlignmentBytes();
+ RETURN_STATUS_IF(alignment > kMaxAlignment, Status::INVALID_ARGUMENT);
+ RETURN_STATUS_IF(kMaxAlignment % alignment, Status::INVALID_ARGUMENT);
+ RETURN_STATUS_IF(len % alignment, Status::INVALID_ARGUMENT);
+
+ uint8_t buffer[kMaxAlignment];
+ uint8_t erased_pattern_buffer[kMaxAlignment];
+ size_t offset = 0;
+ memset(erased_pattern_buffer,
+ flash_.GetErasedMemoryContent(),
+ sizeof(erased_pattern_buffer));
+ *is_erased = false;
+ while (len > 0) {
+ // Check earlier that len is aligned, no need to round up
+ uint16_t read_size = std::min(static_cast<uint32_t>(sizeof(buffer)), len);
+ RETURN_IF_ERROR(Read(buffer, source_flash_address + offset, read_size));
+ if (memcmp(buffer, erased_pattern_buffer, read_size)) {
+ // Detected memory chunk is not entirely erased
+ return Status::OK;
+ }
+ offset += read_size;
+ len -= read_size;
+ }
+ *is_erased = true;
+ return Status::OK;
+ }
+
+ constexpr uint32_t GetSectorSizeBytes() const {
+ return flash_.GetSectorSizeBytes();
+ }
+
+ uint32_t GetSizeBytes() const {
+ return GetSectorCount() * GetSectorSizeBytes();
+ }
+
+ virtual uint8_t GetAlignmentBytes() const {
+ return flash_.GetAlignmentBytes();
+ }
+
+ virtual uint32_t GetSectorCount() 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.
+ uint8_t* PartitionAddressToMcuAddress(Address address) const {
+ return flash_.FlashAddressToMcuAddress(PartitionToFlashAddress(address));
+ }
+
+ FlashMemory::Address PartitionToFlashAddress(Address address) const {
+ return flash_.GetStartAddress() +
+ (start_sector_index_ - flash_.GetStartSector()) *
+ GetSectorSizeBytes() +
+ address;
+ }
+
+ protected:
+ Status CheckBounds(Address address, uint32_t len) const {
+ if (address + len > GetSizeBytes()) {
+ LOG(ERROR) << "Attempted out-of-bound flash memory access (address:"
+ << address << " length:" << len << ")";
+ return Status::INVALID_ARGUMENT;
+ }
+ return Status::OK;
+ }
+
+ private:
+ FlashMemory& flash_;
+ const uint32_t start_sector_index_;
+ const uint32_t sector_count_;
+ const PartitionPermission permission_;
+};
+
+// FlashSubPartition defines a new partition which maps itself as a smaller
+// piece of another partition. This can used when a partition has special
+// behaviours (for example encrypted flash).
+// For example, this will be the first sector of test_partition:
+// FlashSubPartition test_partition_sector1(&test_partition, 0, 1);
+class FlashSubPartition : public FlashPartition {
+ public:
+ constexpr FlashSubPartition(FlashPartition* parent_partition,
+ uint32_t start_sector_index,
+ uint32_t sector_count)
+ : FlashPartition(*parent_partition),
+ partition_(parent_partition),
+ start_sector_index_(start_sector_index),
+ sector_count_(sector_count) {}
+
+ Status Erase(Address address, uint32_t num_sectors) override {
+ RETURN_IF_ERROR(CheckBounds(address, num_sectors * GetSectorSizeBytes()));
+ return partition_->Erase(ParentAddress(address), num_sectors);
+ }
+
+ Status Read(uint8_t* destination_ram_address,
+ Address source_flash_address,
+ uint32_t len) override {
+ RETURN_IF_ERROR(CheckBounds(source_flash_address, len));
+ return partition_->Read(
+ destination_ram_address, ParentAddress(source_flash_address), len);
+ }
+
+ Status Write(Address destination_flash_address,
+ const uint8_t* source_ram_address,
+ uint32_t len) override {
+ RETURN_IF_ERROR(CheckBounds(destination_flash_address, len));
+ return partition_->Write(
+ ParentAddress(destination_flash_address), source_ram_address, len);
+ }
+
+ Status IsChunkErased(Address source_flash_address,
+ uint32_t len,
+ bool* is_erased) override {
+ RETURN_IF_ERROR(CheckBounds(source_flash_address, len));
+ return partition_->IsChunkErased(
+ ParentAddress(source_flash_address), len, is_erased);
+ }
+
+ uint8_t GetAlignmentBytes() const override {
+ return partition_->GetAlignmentBytes();
+ }
+
+ uint32_t GetSectorCount() const override { return sector_count_; }
+
+ private:
+ Address ParentAddress(Address address) const {
+ return address + start_sector_index_ * partition_->GetSectorSizeBytes();
+ }
+ FlashPartition* partition_;
+ const uint32_t start_sector_index_;
+ const uint32_t sector_count_;
+};
+
+} // namespace pw