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