| // 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. |
| |
| #include "pw_kvs/flash_memory.h" |
| |
| #include <algorithm> |
| #include <cinttypes> |
| #include <cstring> |
| |
| #include "pw_kvs_private/macros.h" |
| #include "pw_log/log.h" |
| |
| namespace pw::kvs { |
| |
| using std::byte; |
| |
| Status FlashPartition::Erase(Address address, size_t num_sectors) { |
| if (permission_ == PartitionPermission::kReadOnly) { |
| return Status::PERMISSION_DENIED; |
| } |
| |
| TRY(CheckBounds(address, num_sectors * sector_size_bytes())); |
| return flash_.Erase(PartitionToFlashAddress(address), num_sectors); |
| } |
| |
| StatusWithSize FlashPartition::Read(Address address, span<byte> output) { |
| TRY(CheckBounds(address, output.size())); |
| return flash_.Read(PartitionToFlashAddress(address), output); |
| } |
| |
| StatusWithSize FlashPartition::Write(Address address, span<const byte> data) { |
| if (permission_ == PartitionPermission::kReadOnly) { |
| return Status::PERMISSION_DENIED; |
| } |
| TRY(CheckBounds(address, data.size())); |
| return flash_.Write(PartitionToFlashAddress(address), data); |
| } |
| |
| StatusWithSize FlashPartition::Write( |
| const Address start_address, std::initializer_list<span<const byte>> data) { |
| byte buffer[64]; // TODO: Configure this? |
| |
| Address address = start_address; |
| auto bytes_written = [&]() { return address - start_address; }; |
| |
| const size_t write_size = AlignDown(sizeof(buffer), alignment_bytes()); |
| size_t bytes_in_buffer = 0; |
| |
| for (span<const byte> chunk : data) { |
| while (!chunk.empty()) { |
| const size_t to_copy = |
| std::min(write_size - bytes_in_buffer, chunk.size()); |
| |
| std::memcpy(&buffer[bytes_in_buffer], chunk.data(), to_copy); |
| chunk = chunk.subspan(to_copy); |
| bytes_in_buffer += to_copy; |
| |
| // If the buffer is full, write it out. |
| if (bytes_in_buffer == write_size) { |
| Status status = Write(address, span(buffer, write_size)); |
| if (!status.ok()) { |
| return StatusWithSize(status, bytes_written()); |
| } |
| |
| address += write_size; |
| bytes_in_buffer = 0; |
| } |
| } |
| } |
| |
| // If data remains in the buffer, pad it to the alignment size and flush |
| // the remaining data. |
| if (bytes_in_buffer != 0u) { |
| size_t remaining_write_size = AlignUp(bytes_in_buffer, alignment_bytes()); |
| std::memset( |
| &buffer[bytes_in_buffer], 0, remaining_write_size - bytes_in_buffer); |
| if (Status status = Write(address, span(buffer, remaining_write_size)); |
| !status.ok()) { |
| return StatusWithSize(status, bytes_written()); |
| } |
| address += remaining_write_size; |
| } |
| |
| return StatusWithSize(bytes_written()); |
| } |
| |
| Status FlashPartition::IsRegionErased(Address source_flash_address, |
| size_t length, |
| bool* is_erased) { |
| // Max alignment is artificial to keep the stack usage low for this |
| // function. Using 16 because it's the alignment of encrypted flash. |
| constexpr size_t kMaxAlignment = 16; |
| |
| // Relying on Read() to check address and len arguments. |
| if (is_erased == nullptr) { |
| return Status::INVALID_ARGUMENT; |
| } |
| const size_t alignment = alignment_bytes(); |
| if (alignment > kMaxAlignment || kMaxAlignment % alignment || |
| length % alignment) { |
| return Status::INVALID_ARGUMENT; |
| } |
| |
| byte buffer[kMaxAlignment]; |
| byte erased_pattern_buffer[kMaxAlignment]; |
| |
| size_t offset = 0; |
| std::memset(erased_pattern_buffer, |
| int(flash_.erased_memory_content()), |
| sizeof(erased_pattern_buffer)); |
| *is_erased = false; |
| while (length > 0u) { |
| // Check earlier that length is aligned, no need to round up |
| size_t read_size = std::min(sizeof(buffer), length); |
| TRY(Read(source_flash_address + offset, read_size, buffer).status()); |
| if (std::memcmp(buffer, erased_pattern_buffer, read_size)) { |
| // Detected memory chunk is not entirely erased |
| return Status::OK; |
| } |
| offset += read_size; |
| length -= read_size; |
| } |
| *is_erased = true; |
| return Status::OK; |
| } |
| |
| Status FlashPartition::CheckBounds(Address address, size_t length) const { |
| if (address + length > size_bytes()) { |
| PW_LOG_ERROR("Attempted out-of-bound flash memory access (address: %" PRIu32 |
| " length: %zu)", |
| address, |
| length); |
| return Status::INVALID_ARGUMENT; |
| } |
| return Status::OK; |
| } |
| |
| } // namespace pw::kvs |