aboutsummaryrefslogtreecommitdiff
path: root/pw_kvs/flash_memory.cc
blob: b5b72ae99c76dbea454505ce9e7a7e2b81fcbd03 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// 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::OUT_OF_RANGE;
  }
  return Status::OK;
}

}  // namespace pw::kvs