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 "aml-bad-block.h"
6
7#include <stdlib.h>
8
9#ifdef TEST
10#include <unittest/unittest.h>
11#define zxlogf(flags, ...) unittest_printf(__VA_ARGS__)
12#else
13#include <ddk/debug.h>
14#endif
15#include <ddk/protocol/nand.h>
16
17#include <fbl/algorithm.h>
18#include <fbl/alloc_checker.h>
19#include <fbl/auto_lock.h>
20#include <lib/sync/completion.h>
21
22namespace nand {
23
24namespace {
25
26constexpr uint32_t kBadBlockTableMagic = 0x7462626E; // "nbbt"
27
28struct BlockOperationContext {
29    sync_completion_t* completion_event;
30    zx_status_t status;
31};
32
33void CompletionCallback(nand_op_t* op, zx_status_t status) {
34    auto* ctx = static_cast<BlockOperationContext*>(op->cookie);
35
36    zxlogf(TRACE, "Completion status: %d\n", status);
37    ctx->status = status;
38    sync_completion_signal(ctx->completion_event);
39    return;
40}
41
42} // namespace
43
44zx_status_t AmlBadBlock::Create(Config config, fbl::RefPtr<BadBlock>* out) {
45    // Query parent to get its nand_info_t and size for nand_op_t.
46    nand_info_t nand_info;
47    size_t parent_op_size;
48    config.nand_proto.ops->query(config.nand_proto.ctx, &nand_info, &parent_op_size);
49
50    // Allocate nand_op.
51    fbl::AllocChecker ac;
52    fbl::Array<uint8_t> nand_op(new (&ac) uint8_t[parent_op_size], parent_op_size);
53    if (!ac.check()) {
54        return ZX_ERR_NO_MEMORY;
55    }
56
57    // Allocate VMOs.
58    const uint32_t table_len = fbl::round_up(nand_info.num_blocks, nand_info.page_size);
59    zx::vmo data_vmo;
60    zx_status_t status = zx::vmo::create(table_len, 0, &data_vmo);
61    if (status != ZX_OK) {
62        zxlogf(ERROR, "nandpart: Failed to create VMO for bad block table\n");
63        return status;
64    }
65
66    const uint32_t bbt_page_count = table_len / nand_info.page_size;
67    zx::vmo oob_vmo;
68    status = zx::vmo::create(sizeof(OobMetadata) * bbt_page_count, 0, &oob_vmo);
69    if (status != ZX_OK) {
70        zxlogf(ERROR, "nandpart: Failed to create VMO for oob metadata\n");
71        return status;
72    }
73
74    // Map them.
75    constexpr uint32_t kPermissions = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
76    uintptr_t vaddr_table;
77    status = zx::vmar::root_self()->map(0, data_vmo, 0, table_len, kPermissions, &vaddr_table);
78    if (status != ZX_OK) {
79        zxlogf(ERROR, "nandpart: Failed to map VMO for bad block table\n");
80        return status;
81    }
82
83    uintptr_t vaddr_oob;
84    status = zx::vmar::root_self()->map(0, oob_vmo, 0, sizeof(OobMetadata) * bbt_page_count,
85                                       kPermissions, &vaddr_oob);
86    if (status != ZX_OK) {
87        zxlogf(ERROR, "nandpart: Failed to map VMO for oob metadata\n");
88        return status;
89    }
90
91    // Construct all the things.
92    *out = fbl::MakeRefCountedChecked<AmlBadBlock>(&ac, fbl::move(data_vmo), fbl::move(oob_vmo),
93                                                   fbl::move(nand_op), config, nand_info,
94                                                   reinterpret_cast<BlockStatus*>(vaddr_table),
95                                                   table_len,
96                                                   reinterpret_cast<OobMetadata*>(vaddr_oob));
97    if (!ac.check()) {
98        return ZX_ERR_NO_MEMORY;
99    }
100    return ZX_OK;
101}
102
103zx_status_t AmlBadBlock::EraseBlock(uint32_t block) {
104    sync_completion_t completion;
105    BlockOperationContext op_ctx = {.completion_event = &completion,
106                                    .status = ZX_ERR_INTERNAL};
107    auto* nand_op = reinterpret_cast<nand_op_t*>(nand_op_.get());
108    nand_op->erase.command = NAND_OP_ERASE;
109    nand_op->erase.first_block = block;
110    nand_op->erase.num_blocks = 1;
111    nand_op->completion_cb = CompletionCallback;
112    nand_op->cookie = &op_ctx;
113    nand_.Queue(nand_op);
114
115    // Wait on completion.
116    sync_completion_wait(&completion, ZX_TIME_INFINITE);
117    return op_ctx.status;
118}
119
120zx_status_t AmlBadBlock::GetNewBlock() {
121    for (;;) {
122        // Find a block with the least number of PE cycles.
123        uint16_t least_pe_cycles = UINT16_MAX;
124        uint32_t index = kBlockListMax;
125        for (uint32_t i = 0; i < kBlockListMax; i++) {
126            if (block_list_[i].valid &&
127                &block_list_[i] != block_entry_ &&
128                block_list_[i].program_erase_cycles < least_pe_cycles) {
129                least_pe_cycles = block_list_[i].program_erase_cycles;
130                index = i;
131            }
132        }
133        if (index == kBlockListMax) {
134            zxlogf(ERROR, "nandpart: Unable to find a valid block to store BBT into\n");
135            return ZX_ERR_NOT_FOUND;
136        }
137
138        // Make sure we aren't trying to write to a bad block.
139        const uint32_t block = block_list_[index].block;
140        if (bad_block_table_[block] != kNandBlockGood) {
141            // Try again.
142            block_list_[index].valid = false;
143            continue;
144        }
145
146        // Erase the block before using it.
147        const zx_status_t status = EraseBlock(block);
148        if (status != ZX_OK) {
149            zxlogf(ERROR, "nandpart: Failed to erase block %u, marking bad\n", block);
150            // Mark the block as bad and try again.
151            bad_block_table_[block] = kNandBlockBad;
152            block_list_[index].valid = false;
153            continue;
154        }
155
156        zxlogf(INFO, "nandpart: Moving BBT to block %u\n", block);
157        block_entry_ = &block_list_[index];
158        block_list_[index].program_erase_cycles++;
159        page_ = 0;
160        return ZX_OK;
161    }
162}
163
164zx_status_t AmlBadBlock::WritePages(uint32_t nand_page, uint32_t num_pages) {
165    sync_completion_t completion;
166    BlockOperationContext op_ctx = {.completion_event = &completion,
167                                    .status = ZX_ERR_INTERNAL};
168
169    auto* nand_op = reinterpret_cast<nand_op_t*>(nand_op_.get());
170    nand_op->rw.command = NAND_OP_WRITE;
171    nand_op->rw.data_vmo = data_vmo_.get();
172    nand_op->rw.oob_vmo = oob_vmo_.get();
173    nand_op->rw.length = num_pages;
174    nand_op->rw.offset_nand = nand_page;
175    nand_op->rw.offset_data_vmo = 0;
176    nand_op->rw.offset_oob_vmo = 0;
177    nand_op->completion_cb = CompletionCallback;
178    nand_op->cookie = &op_ctx;
179    nand_.Queue(nand_op);
180
181    // Wait on completion.
182    sync_completion_wait(&completion, ZX_TIME_INFINITE);
183    return op_ctx.status;
184}
185
186zx_status_t AmlBadBlock::WriteBadBlockTable(bool use_new_block) {
187    ZX_DEBUG_ASSERT(bad_block_table_len_ % nand_info_.page_size == 0);
188    const uint32_t bbt_page_count = bad_block_table_len_ / nand_info_.page_size;
189
190    for (;;) {
191        if (use_new_block ||
192            bad_block_table_[block_entry_->block] != kNandBlockGood ||
193            page_ + bbt_page_count >= nand_info_.pages_per_block) {
194            // Current BBT is in a bad block, or it is full, so we must find a new one.
195            use_new_block = false;
196            zxlogf(INFO, "nandpart: Finding a new block to store BBT into\n");
197            const zx_status_t status = GetNewBlock();
198            if (status != ZX_OK) {
199                return status;
200            }
201        }
202
203        // Perform write.
204        for (auto* oob = oob_; oob < oob_ + bbt_page_count; oob++) {
205            oob->magic = kBadBlockTableMagic;
206            oob->program_erase_cycles = block_entry_->program_erase_cycles;
207            oob->generation = generation_;
208        }
209
210        const uint32_t block = block_entry_->block;
211        const uint32_t nand_page = (block * nand_info_.pages_per_block) + page_;
212        const zx_status_t status = WritePages(nand_page, bbt_page_count);
213        if (status != ZX_OK) {
214            zxlogf(ERROR, "nandpart: BBT write failed. Marking %u bad and trying again\n",
215                   block);
216            bad_block_table_[block] = kNandBlockBad;
217            continue;
218        }
219        zxlogf(TRACE, "nandpart: BBT write to block %u pages [%u, %u) successful\n", block,
220               page_, page_ + bbt_page_count);
221        break;
222    }
223
224    page_ += bbt_page_count;
225    generation_++;
226    return ZX_OK;
227}
228
229zx_status_t AmlBadBlock::ReadPages(uint32_t nand_page, uint32_t num_pages) {
230    sync_completion_t completion;
231    BlockOperationContext op_ctx = {.completion_event = &completion,
232                                    .status = ZX_ERR_INTERNAL};
233    auto* nand_op = reinterpret_cast<nand_op_t*>(nand_op_.get());
234    nand_op->rw.command = NAND_OP_READ;
235    nand_op->rw.data_vmo = data_vmo_.get();
236    nand_op->rw.oob_vmo = oob_vmo_.get();
237    nand_op->rw.length = num_pages;
238    nand_op->rw.offset_nand = nand_page;
239    nand_op->rw.offset_data_vmo = 0;
240    nand_op->rw.offset_oob_vmo = 0;
241    nand_op->completion_cb = CompletionCallback;
242    nand_op->cookie = &op_ctx;
243    nand_.Queue(nand_op);
244
245    // Wait on completion.
246    sync_completion_wait(&completion, ZX_TIME_INFINITE);
247    return op_ctx.status;
248}
249
250zx_status_t AmlBadBlock::FindBadBlockTable() {
251    zxlogf(TRACE, "nandpart: Finding bad block table\n");
252
253    if (sizeof(OobMetadata) > nand_info_.oob_size) {
254        zxlogf(ERROR, "nandpart: OOB is too small. Need %zu, found %u\n", sizeof(OobMetadata),
255               nand_info_.oob_size);
256        return ZX_ERR_NOT_SUPPORTED;
257    }
258
259    zxlogf(TRACE, "nandpart: Starting in block %u. Ending in block %u.\n",
260           config_.aml.table_start_block, config_.aml.table_end_block);
261
262    const uint32_t blocks = config_.aml.table_end_block - config_.aml.table_start_block;
263    if (blocks == 0 || blocks > kBlockListMax) {
264        // Driver assumption that no more than |kBlockListMax| blocks will be dedicated for BBT use.
265        zxlogf(ERROR, "Unsupported number of blocks used for BBT.\n");
266        return ZX_ERR_NOT_SUPPORTED;
267    }
268
269    // First find the block the BBT lives in.
270    ZX_DEBUG_ASSERT(bad_block_table_len_ % nand_info_.page_size == 0);
271    const uint32_t bbt_page_count = bad_block_table_len_ / nand_info_.page_size;
272
273    int8_t valid_blocks = 0;
274    block_entry_ = NULL;
275    uint32_t block = config_.aml.table_start_block;
276    for (; block <= config_.aml.table_end_block; block++) {
277        //  Attempt to read up to 6 entries to see if block is valid.
278        uint32_t nand_page = block * nand_info_.pages_per_block;
279        zx_status_t status = ZX_ERR_INTERNAL;
280        for (uint32_t i = 0; i < 6 && status != ZX_OK; i++, nand_page += bbt_page_count) {
281            status = ReadPages(nand_page, 1);
282        }
283        if (status != ZX_OK) {
284            // This block is untrustworthy. Do not add it to the block list.
285            // TODO(surajmalhotra): Should we somehow mark this block as bad or
286            // try erasing it?
287            zxlogf(ERROR, "nandpart: Unable to read any pages in block %u\n", block);
288            continue;
289        }
290
291        zxlogf(TRACE, "Successfully read block %u.\n", block);
292
293        block_list_[valid_blocks].block = block;
294        block_list_[valid_blocks].valid = true;
295
296        // If block has valid BBT entries, see if it has the latest entries.
297        if (oob_->magic == kBadBlockTableMagic) {
298            if (oob_->generation >= generation_) {
299                zxlogf(TRACE, "Block %u has valid BBT entries!\n", block);
300                block_entry_ = &block_list_[valid_blocks];
301                generation_ = oob_->generation;
302            }
303            block_list_[valid_blocks].program_erase_cycles = oob_->program_erase_cycles;
304        } else if (oob_->magic == 0xFFFFFFFF) {
305            // Page is erased.
306            block_list_[valid_blocks].program_erase_cycles = 0;
307        } else {
308            zxlogf(ERROR, "Block %u is neither erased, nor contains a valid entry!\n", block);
309            block_list_[valid_blocks].program_erase_cycles = oob_->program_erase_cycles;
310        }
311
312        valid_blocks++;
313    }
314
315    if (block_entry_ == NULL) {
316        zxlogf(ERROR, "nandpart: No valid BBT entries found!\n");
317        // TODO(surajmalhotra): Initialize the BBT by reading the factory bad
318        // blocks.
319        return ZX_ERR_INTERNAL;
320    }
321
322    for (size_t idx = valid_blocks - 1; idx < kBlockListMax; idx++) {
323        block_list_[idx].valid = false;
324    }
325
326    zxlogf(TRACE, "nandpart: Finding last BBT in block %u\n", block_entry_->block);
327
328    // Next find the last valid BBT entry in block.
329    bool found_one = false;
330    bool latest_entry_bad = true;
331    uint32_t page = 0;
332    bool break_loop = false;
333    for (; page + bbt_page_count <= nand_info_.pages_per_block; page += bbt_page_count) {
334        zx_status_t status = ZX_OK;
335        // Check that all pages in current bbt_page_count are valid.
336        zxlogf(TRACE, "Reading pages [%u, %u)\n", page, page + bbt_page_count);
337        const uint32_t nand_page = block_entry_->block * nand_info_.pages_per_block + page;
338        status = ReadPages(nand_page, bbt_page_count);
339        if (status != ZX_OK) {
340            // It's fine for entries to be unreadable as long as future ones are
341            // readable.
342            zxlogf(TRACE, "nandpart: Unable to read page %u\n", page);
343            latest_entry_bad = true;
344            continue;
345        }
346        for (uint32_t i = 0; i < bbt_page_count; i++) {
347            if ((oob_ + i)->magic != kBadBlockTableMagic) {
348                // Last BBT entry in table was found, so quit looking at future entries.
349                zxlogf(TRACE, "nandpart: Page %u does not contain valid BBT entry\n", page + i);
350                break_loop = true;
351                break;
352            }
353        }
354        if (break_loop) {
355            break;
356        }
357        // Store latest complete BBT.
358        zxlogf(TRACE, "BBT entry in pages (%u, %u] is valid\n", page, page + bbt_page_count);
359        latest_entry_bad = false;
360        found_one = true;
361        page_ = page;
362        generation_ = static_cast<uint16_t>(oob_->generation + 1);
363    }
364
365    if (!found_one) {
366        zxlogf(ERROR, "nandpart: Unable to find a valid copy of the bad block table\n");
367        return ZX_ERR_NOT_FOUND;
368    }
369
370    if (page + bbt_page_count <= nand_info_.pages_per_block || latest_entry_bad) {
371        // Last iteration failed to read valid copy of BBT (that's how loop exited),
372        // so we need to reread the BBT.
373        const uint32_t nand_page = block_entry_->block * nand_info_.pages_per_block + page_;
374        const zx_status_t status = ReadPages(nand_page, bbt_page_count);
375        if (status != ZX_OK) {
376            zxlogf(ERROR, "nandpart: Unable to re-read latest copy of bad block table\n");
377            return status;
378        }
379        for (uint32_t i = 0; i < bbt_page_count; i++) {
380            if ((oob_ + i)->magic != kBadBlockTableMagic) {
381                zxlogf(ERROR, "nandpart: Latest copy of bad block table no longer valid?\n");
382                return ZX_ERR_INTERNAL;
383            }
384        }
385
386        if (latest_entry_bad) {
387            zxlogf(ERROR, "nandpart: Latest entry in block %u is invalid. Moving bad block file.\n",
388                   block_entry_->block);
389            constexpr bool kUseNewBlock = true;
390            const zx_status_t status = WriteBadBlockTable(kUseNewBlock);
391            if (status != ZX_OK) {
392                return status;
393            }
394        } else {
395            // Page needs to point to next available slot.
396            zxlogf(INFO, "nandpart: Latest BBT entry found in pages [%u, %u)\n", page_,
397                   page + bbt_page_count);
398            page_ += bbt_page_count;
399        }
400    }
401
402    table_valid_ = true;
403    return ZX_OK;
404}
405
406zx_status_t AmlBadBlock::GetBadBlockList(uint32_t first_block, uint32_t last_block,
407                                         fbl::Array<uint32_t>* bad_blocks) {
408    fbl::AutoLock al(&lock_);
409    if (!table_valid_) {
410        const zx_status_t status = FindBadBlockTable();
411        if (status != ZX_OK) {
412            return status;
413        }
414    }
415
416    if (first_block >= nand_info_.num_blocks || last_block > nand_info_.num_blocks) {
417        return ZX_ERR_INVALID_ARGS;
418    }
419
420    // Scan BBT for bad block list.
421    size_t bad_block_count = 0;
422    for (uint32_t block = first_block; block < last_block; block++) {
423        if (bad_block_table_[block] != kNandBlockGood) {
424            bad_block_count += 1;
425        }
426    }
427
428    // Early return if no bad blocks found.
429    if (bad_block_count == 0) {
430        bad_blocks->reset();
431        return ZX_OK;
432    }
433
434    // Allocate array and copy list.
435    fbl::AllocChecker ac;
436    bad_blocks->reset(new (&ac) uint32_t[bad_block_count], bad_block_count);
437    if (!ac.check()) {
438        return ZX_ERR_NO_MEMORY;
439    }
440
441    bad_block_count = 0;
442    for (uint32_t block = first_block; block < last_block; block++) {
443        if (bad_block_table_[block] != kNandBlockGood) {
444            (*bad_blocks)[bad_block_count++] = block;
445        }
446    }
447
448    return ZX_OK;
449}
450
451zx_status_t AmlBadBlock::MarkBlockBad(uint32_t block) {
452    fbl::AutoLock al(&lock_);
453    if (!table_valid_) {
454        const zx_status_t status = FindBadBlockTable();
455        if (status != ZX_OK) {
456            return status;
457        }
458    }
459
460    if (block > nand_info_.num_blocks) {
461        return ZX_ERR_OUT_OF_RANGE;
462    }
463
464    // Early return if block is already marked bad.
465    if (bad_block_table_[block] != kNandBlockGood) {
466        return ZX_OK;
467    }
468    bad_block_table_[block] = kNandBlockBad;
469
470    constexpr bool kNoUseNewBlock = false;
471    return WriteBadBlockTable(kNoUseNewBlock);
472}
473
474} // namespace nand
475