1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ndm-ram-driver.h"
6
7 #include <stdio.h>
8 #include <string.h>
9
10 namespace {
11
12 constexpr uint8_t kWrittenFlag = 1 << 0;
13 constexpr uint8_t kFailEccFlag = 1 << 1;
14 constexpr uint8_t kBadBlockFlag = 1 << 2;
15
16 // Technically, can be used to set / reset more than one flag at a time.
SetFlag(uint8_t flag,uint8_t * where)17 void SetFlag(uint8_t flag, uint8_t* where) {
18 *where = *where | flag;
19 }
20
ClearFlag(uint8_t flag,uint8_t * where)21 void ClearFlag(uint8_t flag, uint8_t* where) {
22 *where = *where & static_cast<uint8_t>(~flag);
23 }
24
IsFlagSet(uint8_t flag,const uint8_t * where)25 bool IsFlagSet(uint8_t flag, const uint8_t* where) {
26 return (*where & flag) == flag;
27 }
28
29 } // namespace
30
Init()31 const char* NdmRamDriver::Init() {
32 size_t num_pages = PagesPerBlock() * options_.num_blocks;
33 size_t volume_size = num_pages * (options_.page_size + options_.eb_size);
34 volume_ = fbl::Array<uint8_t>(new uint8_t[volume_size], volume_size);
35 flags_ = fbl::Array<uint8_t>(new uint8_t[num_pages], num_pages);
36 memset(volume_.get(), 0xff, volume_size);
37 memset(flags_.get(), 0, num_pages);
38
39 return nullptr;
40 }
41
Attach(const ftl::Volume * ftl_volume)42 const char* NdmRamDriver::Attach(const ftl::Volume* ftl_volume) {
43 return CreateNdmVolume(ftl_volume, options_);
44 }
45
Detach()46 bool NdmRamDriver::Detach() {
47 return RemoveNdmVolume();
48 }
49
50 // Returns kNdmOk, kNdmUncorrectableEcc, kNdmFatalError or kNdmUnsafeEcc.
NandRead(uint32_t start_page,uint32_t page_count,void * page_buffer,void * oob_buffer)51 int NdmRamDriver::NandRead(uint32_t start_page, uint32_t page_count, void* page_buffer,
52 void* oob_buffer) {
53 bool unsafe = false;
54 uint8_t* data = reinterpret_cast<uint8_t*>(page_buffer);
55 uint8_t* spare = reinterpret_cast<uint8_t*>(oob_buffer);
56 for (; page_count; page_count--, start_page++) {
57 int result = ReadPage(start_page, data, spare);
58 if (result == ftl::kNdmUnsafeEcc) {
59 unsafe = true;
60 } else if (result != ftl::kNdmOk) {
61 return result;
62 }
63
64 if (data) {
65 data += options_.page_size;
66 }
67 if (spare) {
68 spare += options_.eb_size;
69 }
70 }
71
72 return unsafe ? ftl::kNdmUnsafeEcc : ftl::kNdmOk;
73 }
74
75 // Returns kNdmOk, kNdmError or kNdmFatalError. kNdmError triggers marking the block as bad.
NandWrite(uint32_t start_page,uint32_t page_count,const void * page_buffer,const void * oob_buffer)76 int NdmRamDriver::NandWrite(uint32_t start_page, uint32_t page_count,
77 const void* page_buffer, const void* oob_buffer) {
78 const uint8_t* data = reinterpret_cast<const uint8_t*>(page_buffer);
79 const uint8_t* spare = reinterpret_cast<const uint8_t*>(oob_buffer);
80 ZX_ASSERT(data);
81 ZX_ASSERT(spare);
82 for (; page_count; page_count--, start_page++) {
83 int result = WritePage(start_page, data, spare);
84 if (result != ftl::kNdmOk) {
85 return result;
86 }
87 data += options_.page_size;
88 spare += options_.eb_size;
89 }
90 return ftl::kNdmOk;
91 }
92
93 // Returns kNdmOk or kNdmError. kNdmError triggers marking the block as bad.
NandErase(uint32_t page_num)94 int NdmRamDriver::NandErase(uint32_t page_num) {
95 ZX_ASSERT(page_num < options_.block_size * options_.num_blocks);
96 if (BadBlock(page_num)) {
97 ZX_ASSERT(false);
98 }
99
100 if (SimulateBadBlock(page_num)) {
101 return ftl::kNdmError;
102 }
103
104 // Reset block data and spare area.
105 ZX_ASSERT(page_num % PagesPerBlock() == 0);
106 uint32_t end = page_num + PagesPerBlock();
107 do {
108 memset(MainData(page_num), 0xFF, options_.page_size);
109 memset(SpareData(page_num), 0xFF, options_.eb_size);
110 SetWritten(page_num, false);
111 SetFailEcc(page_num, false);
112 } while (++page_num < end);
113
114 return ftl::kNdmOk;
115 }
116
117 // Returns kTrue, kFalse or kNdmError.
IsBadBlock(uint32_t page_num)118 int NdmRamDriver::IsBadBlock(uint32_t page_num) {
119 ZX_ASSERT(page_num < options_.block_size * options_.num_blocks);
120 ZX_ASSERT(page_num % PagesPerBlock() == 0);
121
122 // If first byte on first page is not all 0xFF, block is bad.
123 // This is a common (although not unique) factory marking used by real NAND
124 // chips. This code enables a test to simulate factory-bad blocks.
125 if (SpareData(page_num)[0] != 0xFF) {
126 SetBadBlock(page_num, true);
127 return ftl::kTrue;
128 }
129 return ftl::kFalse;
130 }
131
IsEmptyPage(uint32_t page_num,const uint8_t * data,const uint8_t * spare)132 bool NdmRamDriver::IsEmptyPage(uint32_t page_num, const uint8_t* data, const uint8_t* spare) {
133 ZX_ASSERT(page_num < options_.block_size * options_.num_blocks);
134 if (!Written(page_num)) {
135 return true;
136 }
137 return IsEmptyPageImpl(data, options_.page_size, spare, options_.eb_size);
138 }
139
ReadPage(uint32_t page_num,uint8_t * data,uint8_t * spare)140 int NdmRamDriver::ReadPage(uint32_t page_num, uint8_t* data, uint8_t* spare) {
141 ZX_ASSERT(page_num < options_.block_size * options_.num_blocks);
142 // Fail ECC if page never written or was failed before.
143 if (data && !Written(page_num)) {
144 // Reading FF is definitely OK at least for spare data.
145 return ftl::kNdmUncorrectableEcc;
146 }
147
148 if (FailEcc(page_num)) {
149 return ftl::kNdmUncorrectableEcc;
150 }
151
152 if (data) {
153 // Read page main data.
154 memcpy(data, MainData(page_num), options_.page_size);
155 }
156
157 if (spare) {
158 // Read page main data.
159 memcpy(spare, SpareData(page_num), options_.eb_size);
160 }
161
162 // Return an occasional kNdmUnsafeEcc.
163 if (ecc_error_interval_++ == kEccErrorInterval) {
164 ecc_error_interval_ = 0;
165 return ftl::kNdmUnsafeEcc;
166 }
167
168 return ftl::kNdmOk;
169 }
170
WritePage(uint32_t page_num,const uint8_t * data,const uint8_t * spare)171 int NdmRamDriver::WritePage(uint32_t page_num, const uint8_t* data, const uint8_t* spare) {
172 ZX_ASSERT(page_num < options_.block_size * options_.num_blocks);
173 if (BadBlock(page_num)) {
174 ZX_ASSERT(false);
175 }
176
177 ZX_ASSERT(!Written(page_num));
178
179 if (SimulateBadBlock(page_num)) {
180 return ftl::kNdmError;
181 }
182
183 // Write data and spare bytes to 'flash'.
184 memcpy(MainData(page_num), data, options_.page_size);
185 memcpy(SpareData(page_num), spare, options_.eb_size);
186 SetWritten(page_num, true);
187
188 return ftl::kNdmOk;
189 }
190
SimulateBadBlock(uint32_t page_num)191 bool NdmRamDriver::SimulateBadBlock(uint32_t page_num) {
192 if (num_bad_blocks_ < options_.max_bad_blocks) {
193 if (bad_block_interval_++ == kBadBlockInterval) {
194 SetBadBlock(page_num, true);
195 bad_block_interval_ = 0;
196 ++num_bad_blocks_;
197 return true;
198 }
199 }
200 return false;
201 }
202
MainData(uint32_t page_num)203 uint8_t* NdmRamDriver::MainData(uint32_t page_num) {
204 size_t offset = page_num * (options_.page_size + options_.eb_size);
205 ZX_ASSERT(offset < volume_.size());
206 return &volume_[offset];
207 }
208
SpareData(uint32_t page_num)209 uint8_t* NdmRamDriver::SpareData(uint32_t page_num) {
210 return MainData(page_num) + options_.page_size;
211 }
212
Written(uint32_t page_num)213 bool NdmRamDriver::Written(uint32_t page_num) {
214 return IsFlagSet(kWrittenFlag, &flags_[page_num]);
215 }
216
FailEcc(uint32_t page_num)217 bool NdmRamDriver::FailEcc(uint32_t page_num) {
218 return IsFlagSet(kFailEccFlag, &flags_[page_num]);
219 }
220
BadBlock(uint32_t page_num)221 bool NdmRamDriver::BadBlock(uint32_t page_num) {
222 return IsFlagSet(kBadBlockFlag, &flags_[page_num / PagesPerBlock()]);
223 }
224
SetWritten(uint32_t page_num,bool value)225 void NdmRamDriver::SetWritten(uint32_t page_num, bool value) {
226 if (value) {
227 SetFlag(kWrittenFlag, &flags_[page_num]);
228 } else {
229 ClearFlag(kWrittenFlag, &flags_[page_num]);
230 }
231 }
232
SetFailEcc(uint32_t page_num,bool value)233 void NdmRamDriver::SetFailEcc(uint32_t page_num, bool value) {
234 if (value) {
235 SetFlag(kFailEccFlag, &flags_[page_num]);
236 } else {
237 ClearFlag(kFailEccFlag, &flags_[page_num]);
238 }
239 }
240
SetBadBlock(uint32_t page_num,bool value)241 void NdmRamDriver::SetBadBlock(uint32_t page_num, bool value) {
242 // It doesn't really matter where the flag is stored.
243 if (value) {
244 SetFlag(kBadBlockFlag, &flags_[page_num / PagesPerBlock()]);
245 } else {
246 ClearFlag(kBadBlockFlag, &flags_[page_num / PagesPerBlock()]);
247 }
248 }
249
PagesPerBlock() const250 uint32_t NdmRamDriver::PagesPerBlock() const {
251 return options_.block_size / options_.page_size;
252 }
253