| // 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 <array> |
| #include <cstddef> |
| #include <cstring> |
| |
| #include "pw_containers/vector.h" |
| #include "pw_kvs/flash_memory.h" |
| #include "pw_span/span.h" |
| #include "pw_status/status.h" |
| |
| namespace pw::kvs { |
| |
| class FlashError { |
| public: |
| static constexpr FlashMemory::Address kAnyAddress = FlashMemory::Address(-1); |
| static constexpr size_t kAlways = size_t(-1); |
| |
| // Creates a FlashError that always triggers on the next operation. |
| static constexpr FlashError Unconditional(Status status, |
| size_t times = kAlways, |
| size_t delay = 0) { |
| return FlashError(status, kAnyAddress, 0, times, delay); |
| } |
| |
| // Creates a FlashError that triggers for particular addresses. |
| static constexpr FlashError InRange(Status status, |
| FlashMemory::Address address, |
| size_t size = 1, |
| size_t times = kAlways, |
| size_t delay = 0) { |
| return FlashError(status, address, size, times, delay); |
| } |
| |
| // Determines if this FlashError applies to the operation. |
| Status Check(FlashMemory::Address start_address, size_t size); |
| |
| // Determines if any of a series of FlashErrors applies to the operation. |
| static Status Check(span<FlashError> errors, |
| FlashMemory::Address address, |
| size_t size); |
| |
| private: |
| constexpr FlashError(Status status, |
| FlashMemory::Address address, |
| size_t size, // not used if address is kAnyAddress |
| size_t times, |
| size_t delay) |
| : status_(status), |
| begin_(address), |
| end_(address + size), // not used if address is kAnyAddress |
| delay_(delay), |
| remaining_(times) {} |
| |
| const Status status_; |
| |
| const FlashMemory::Address begin_; |
| const FlashMemory::Address end_; // exclusive |
| |
| size_t delay_; |
| size_t remaining_; |
| }; |
| |
| // This uses a buffer to mimic the behaviour of flash (requires erase before |
| // write, checks alignments, and is addressed in sectors). The underlying buffer |
| // is not initialized. |
| class InMemoryFakeFlash : public FlashMemory { |
| private: |
| static Vector<FlashError, 0> no_errors_; |
| |
| public: |
| // Default to 8-bit alignment. |
| static constexpr size_t kDefaultAlignmentBytes = 1; |
| |
| static constexpr std::byte kErasedValue = std::byte{0xff}; |
| |
| InMemoryFakeFlash(span<std::byte> buffer, |
| size_t sector_size, |
| size_t sector_count, |
| size_t alignment_bytes = kDefaultAlignmentBytes, |
| Vector<FlashError>& read_errors = no_errors_, |
| Vector<FlashError>& write_errors = no_errors_) |
| : FlashMemory(sector_size, sector_count, alignment_bytes), |
| buffer_(buffer), |
| read_errors_(read_errors), |
| write_errors_(write_errors) {} |
| |
| // The fake flash is always enabled. |
| Status Enable() override { return Status::OK; } |
| |
| Status Disable() override { return Status::OK; } |
| |
| bool IsEnabled() const override { return true; } |
| |
| // Erase num_sectors starting at a given address. |
| Status Erase(Address address, size_t num_sectors) override; |
| |
| // Reads bytes from flash into buffer. |
| StatusWithSize Read(Address address, span<std::byte> output) override; |
| |
| // Writes bytes to flash. |
| StatusWithSize Write(Address address, span<const std::byte> data) override; |
| |
| // Testing API |
| |
| // Access the underlying buffer for testing purposes. Not part of the |
| // FlashMemory API. |
| span<std::byte> buffer() const { return buffer_; } |
| |
| bool InjectReadError(const FlashError& error) { |
| if (read_errors_.full()) { |
| return false; |
| } |
| read_errors_.push_back(error); |
| return true; |
| } |
| |
| bool InjectWriteError(const FlashError& error) { |
| if (write_errors_.full()) { |
| return false; |
| } |
| write_errors_.push_back(error); |
| return true; |
| } |
| |
| private: |
| const span<std::byte> buffer_; |
| Vector<FlashError>& read_errors_; |
| Vector<FlashError>& write_errors_; |
| }; |
| |
| // Creates an InMemoryFakeFlash backed by a std::array. The array is initialized |
| // to the erased value. A byte array to which to initialize the memory may be |
| // provided. |
| template <size_t kSectorSize, size_t kSectorCount, size_t kInjectedErrors = 8> |
| class FakeFlashBuffer : public InMemoryFakeFlash { |
| public: |
| // Creates a flash memory with no data written. |
| FakeFlashBuffer(size_t alignment_bytes = kDefaultAlignmentBytes) |
| : FakeFlashBuffer(std::array<std::byte, 0>{}, alignment_bytes) {} |
| |
| // Creates a flash memory initialized to the provided contents. |
| FakeFlashBuffer(span<const std::byte> contents, |
| size_t alignment_bytes = kDefaultAlignmentBytes) |
| : InMemoryFakeFlash(buffer_, |
| kSectorSize, |
| kSectorCount, |
| alignment_bytes, |
| read_errors_, |
| write_errors_) { |
| std::memset(buffer_.data(), int(kErasedValue), buffer_.size()); |
| std::memcpy(buffer_.data(), |
| contents.data(), |
| std::min(contents.size(), buffer_.size())); |
| } |
| |
| private: |
| std::array<std::byte, kSectorCount * kSectorSize> buffer_; |
| Vector<FlashError, kInjectedErrors> read_errors_; |
| Vector<FlashError, kInjectedErrors> write_errors_; |
| }; |
| |
| } // namespace pw::kvs |