blob: 217ad16eff2bab23821b3eb127b9c7cee6679c9b [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.
#define PW_LOG_MODULE_NAME "PW_FLASH"
#define PW_LOG_LEVEL PW_KVS_LOG_LEVEL
#include "pw_kvs/flash_memory.h"
#include <algorithm>
#include <cinttypes>
#include <cstring>
#include "pw_assert/check.h"
#include "pw_kvs_private/config.h"
#include "pw_log/log.h"
#include "pw_status/status_with_size.h"
#include "pw_status/try.h"
namespace pw::kvs {
using std::byte;
#if PW_CXX_STANDARD_IS_SUPPORTED(17)
Status FlashPartition::Writer::DoWrite(ConstByteSpan data) {
if (partition_.size_bytes() <= position_) {
return Status::OutOfRange();
}
if (data.size_bytes() > (partition_.size_bytes() - position_)) {
return Status::ResourceExhausted();
}
if (data.size_bytes() == 0) {
return OkStatus();
}
const StatusWithSize sws = partition_.Write(position_, data);
if (sws.ok()) {
position_ += data.size_bytes();
}
return sws.status();
}
StatusWithSize FlashPartition::Reader::DoRead(ByteSpan data) {
if (position_ >= partition_.size_bytes()) {
return StatusWithSize::OutOfRange();
}
size_t bytes_to_read =
std::min(data.size_bytes(), partition_.size_bytes() - position_);
const StatusWithSize sws =
partition_.Read(position_, data.first(bytes_to_read));
if (sws.ok()) {
position_ += bytes_to_read;
}
return sws;
}
#endif // PW_CXX_STANDARD_IS_SUPPORTED(17)
StatusWithSize FlashPartition::Output::DoWrite(std::span<const byte> data) {
PW_TRY_WITH_SIZE(flash_.Write(address_, data));
address_ += data.size();
return StatusWithSize(data.size());
}
StatusWithSize FlashPartition::Input::DoRead(std::span<byte> data) {
StatusWithSize result = flash_.Read(address_, data);
address_ += result.size();
return result;
}
FlashPartition::FlashPartition(
FlashMemory* flash,
uint32_t start_sector_index,
uint32_t sector_count,
uint32_t alignment_bytes, // Defaults to flash alignment
PartitionPermission permission)
: flash_(*flash),
start_sector_index_(start_sector_index),
sector_count_(sector_count),
alignment_bytes_(
alignment_bytes == 0
? flash_.alignment_bytes()
: std::max(alignment_bytes, uint32_t(flash_.alignment_bytes()))),
permission_(permission) {
uint32_t misalignment = (alignment_bytes_ % flash_.alignment_bytes());
PW_DCHECK_UINT_EQ(misalignment,
0,
"Flash partition alignmentmust be a multiple of the flash "
"memory alignment");
}
Status FlashPartition::Erase(Address address, size_t num_sectors) {
if (permission_ == PartitionPermission::kReadOnly) {
return Status::PermissionDenied();
}
PW_TRY(CheckBounds(address, num_sectors * sector_size_bytes()));
const size_t address_sector_offset = address % sector_size_bytes();
PW_CHECK_UINT_EQ(address_sector_offset, 0u);
return flash_.Erase(PartitionToFlashAddress(address), num_sectors);
}
StatusWithSize FlashPartition::Read(Address address, std::span<byte> output) {
PW_TRY_WITH_SIZE(CheckBounds(address, output.size()));
return flash_.Read(PartitionToFlashAddress(address), output);
}
StatusWithSize FlashPartition::Write(Address address,
std::span<const byte> data) {
if (permission_ == PartitionPermission::kReadOnly) {
return StatusWithSize::PermissionDenied();
}
PW_TRY_WITH_SIZE(CheckBounds(address, data.size()));
const size_t address_alignment_offset = address % alignment_bytes();
PW_CHECK_UINT_EQ(address_alignment_offset, 0u);
const size_t size_alignment_offset = data.size() % alignment_bytes();
PW_CHECK_UINT_EQ(size_alignment_offset, 0u);
return flash_.Write(PartitionToFlashAddress(address), data);
}
Status FlashPartition::IsRegionErased(Address source_flash_address,
size_t length,
bool* is_erased) {
// Relying on Read() to check address and len arguments.
if (is_erased == nullptr) {
return Status::InvalidArgument();
}
// TODO(pwbug/214): Currently using a single flash alignment to do both the
// read and write. The allowable flash read length may be less than what write
// needs (possibly by a bunch), resulting in read_buffer and
// erased_pattern_buffer being bigger than they need to be.
const size_t alignment = alignment_bytes();
if (alignment > kMaxFlashAlignment || kMaxFlashAlignment % alignment ||
length % alignment) {
return Status::InvalidArgument();
}
byte read_buffer[kMaxFlashAlignment];
const byte erased_byte = flash_.erased_memory_content();
size_t offset = 0;
*is_erased = false;
while (length > 0u) {
// Check earlier that length is aligned, no need to round up
size_t read_size = std::min(sizeof(read_buffer), length);
PW_TRY(
Read(source_flash_address + offset, read_size, read_buffer).status());
for (byte b : std::span(read_buffer, read_size)) {
if (b != erased_byte) {
// Detected memory chunk is not entirely erased
return OkStatus();
}
}
offset += read_size;
length -= read_size;
}
*is_erased = true;
return OkStatus();
}
bool FlashPartition::AppearsErased(std::span<const byte> data) const {
byte erased_content = flash_.erased_memory_content();
for (byte b : data) {
if (b != erased_content) {
return false;
}
}
return true;
}
Status FlashPartition::CheckBounds(Address address, size_t length) const {
if (address + length > size_bytes()) {
PW_LOG_ERROR(
"FlashPartition - Attempted access (address: %u length: %u), exceeds "
"partition size %u bytes",
unsigned(address),
unsigned(length),
unsigned(size_bytes()));
return Status::OutOfRange();
}
return OkStatus();
}
} // namespace pw::kvs