| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "synaptics-bad-block.h" |
| |
| #include <lib/sync/completion.h> |
| |
| #include <ddk/debug.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_lock.h> |
| |
| namespace { |
| |
| struct BlockOperationContext { |
| sync_completion_t* completion_event; |
| zx_status_t status; |
| }; |
| |
| constexpr uint32_t kBitsPerEntry = 2; |
| constexpr uint32_t kEntriesPerByte = 8 / kBitsPerEntry; |
| |
| constexpr uint8_t kEntryMask = 0x03; |
| |
| constexpr uint8_t kEntryBlockBad = 0x01; |
| constexpr uint8_t kEntryBlockGood = 0x03; |
| |
| bool IsBadBlock(const fbl::Array<uint8_t>& bbt_contents, uint32_t block) { |
| const uint32_t index = block / kEntriesPerByte; |
| ZX_DEBUG_ASSERT(bbt_contents.size() > index); |
| |
| const uint32_t shift = (block - (index * kEntriesPerByte)) * kBitsPerEntry; |
| |
| return ((bbt_contents[index] >> shift) & kEntryMask) != kEntryBlockGood; |
| } |
| |
| void SetBlockBad(fbl::Array<uint8_t>* bbt_contents, uint32_t block) { |
| const uint32_t index = block / kEntriesPerByte; |
| ZX_DEBUG_ASSERT(bbt_contents->size() > index); |
| |
| const uint32_t shift = (block - (index * kEntriesPerByte)) * kBitsPerEntry; |
| |
| (*bbt_contents)[index] = static_cast<uint8_t>((*bbt_contents)[index] & ~(kEntryMask << shift)); |
| (*bbt_contents)[index] = static_cast<uint8_t>((*bbt_contents)[index] | (kEntryBlockBad << shift)); |
| } |
| |
| void CompletionCallback(void* cookie, zx_status_t status, nand_operation_t* /* op */) { |
| auto* ctx = static_cast<BlockOperationContext*>(cookie); |
| ctx->status = status; |
| sync_completion_signal(ctx->completion_event); |
| } |
| |
| } // namespace |
| |
| namespace nand { |
| |
| zx_status_t SynapticsBadBlock::Create(Config config, fbl::RefPtr<BadBlock>* out) { |
| ddk::NandProtocolClient nand(&config.nand_proto); |
| |
| nand_info_t nand_info; |
| size_t parent_op_size = 0; |
| nand.Query(&nand_info, &parent_op_size); |
| |
| if (nand_info.oob_size < kOobSize) { |
| zxlogf(ERROR, "%s: NAND supports only %u OOB bytes, at least %zu are needed", __func__, |
| nand_info.oob_size, kOobSize); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| ZX_DEBUG_ASSERT(nand_info.num_blocks % kEntriesPerByte == 0); |
| ZX_DEBUG_ASSERT(nand_info.num_blocks / kEntriesPerByte <= nand_info.page_size); |
| |
| fbl::AllocChecker ac; |
| fbl::Array<uint8_t> nand_op(new (&ac) uint8_t[parent_op_size], parent_op_size); |
| if (!ac.check()) { |
| zxlogf(ERROR, "%s: Failed to allocate memory for operation", __func__); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx::vmo data_vmo; |
| zx_status_t status = zx::vmo::create(nand_info.page_size, 0, &data_vmo); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to create VMO: %d", __func__, status); |
| return status; |
| } |
| |
| zx::vmo oob_vmo; |
| if ((status = zx::vmo::create(nand_info.oob_size, 0, &oob_vmo)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to create VMO: %d", __func__, status); |
| return status; |
| } |
| |
| *out = fbl::MakeRefCountedChecked<SynapticsBadBlock>(&ac, nand, config.bad_block_config, |
| nand_info, std::move(data_vmo), |
| std::move(oob_vmo), std::move(nand_op)); |
| if (!ac.check()) { |
| zxlogf(ERROR, "%s: Failed to allocate memory for SynapticsBadBlock", __func__); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t SynapticsBadBlock::GetBadBlockList(uint32_t first_block, uint32_t last_block, |
| fbl::Array<uint32_t>* bad_blocks) { |
| fbl::AutoLock al(&lock_); |
| |
| if (!bbt_contents_) { |
| zx_status_t status = ReadBadBlockTable(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| // First loop to count the number of bad blocks, then create the array. |
| |
| size_t bad_block_count = 0; |
| for (uint32_t i = first_block; i <= last_block; i++) { |
| if (IsBadBlock(bbt_contents_, i)) { |
| bad_block_count++; |
| } |
| } |
| |
| if (bad_block_count == 0) { |
| bad_blocks->reset(); |
| return ZX_OK; |
| } |
| |
| fbl::AllocChecker ac; |
| bad_blocks->reset(new (&ac) uint32_t[bad_block_count], bad_block_count); |
| if (!ac.check()) { |
| zxlogf(ERROR, "%s: Failed to allocate memory for bad block array", __func__); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| size_t bad_block_index = 0; |
| for (uint32_t i = first_block; i <= last_block; i++) { |
| if (IsBadBlock(bbt_contents_, i)) { |
| (*bad_blocks)[bad_block_index++] = i; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t SynapticsBadBlock::MarkBlockBad(uint32_t block) { |
| fbl::AutoLock al(&lock_); |
| |
| zx_status_t status; |
| if (!bbt_contents_ && (status = ReadBadBlockTable()) != ZX_OK) { |
| return status; |
| } |
| |
| if (IsBadBlock(bbt_contents_, block)) { |
| // Block is already marked bad. |
| return ZX_OK; |
| } |
| |
| SetBlockBad(&bbt_contents_, block); |
| bbt_version_++; |
| |
| for (bool wrote_bbt = false;;) { |
| if ((status = WriteBadBlockTableToVmo()) != ZX_OK) { |
| return status; |
| } |
| |
| status = WriteBadBlockTable(bbt_block_, InvalidBlock(), kTablePattern, &bbt_block_); |
| if (status == ZX_ERR_IO) { |
| SetBlockBad(&bbt_contents_, bbt_block_); |
| |
| // The bad block table version number can be reused if this first write fails repeatedly. |
| if (wrote_bbt) { |
| bbt_version_++; |
| } |
| |
| wrote_bbt = false; |
| continue; |
| } |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: No good bad block table blocks left", __func__); |
| return ZX_ERR_IO_DATA_LOSS; |
| } |
| |
| wrote_bbt = true; |
| |
| status = WriteBadBlockTable(bbt_mirror_block_, bbt_block_, kMirrorPattern, &bbt_mirror_block_); |
| if (status == ZX_ERR_IO) { |
| SetBlockBad(&bbt_contents_, bbt_mirror_block_); |
| bbt_version_++; |
| } else { |
| if (status != ZX_OK) { |
| zxlogf(WARNING, "%s: Only one good bad block table block left", __func__); |
| } |
| |
| break; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| uint32_t SynapticsBadBlock::FindNextGoodTableBlock(uint32_t start_block, uint32_t except_block) { |
| const uint32_t table_blocks = |
| config_.synaptics.table_end_block - config_.synaptics.table_start_block + 1; |
| |
| // If start_block is valid start searching from start_block + 1, otherwise start searchin from the |
| // beginning of the table. |
| if (IsBlockValid(start_block)) { |
| start_block++; |
| } else { |
| start_block = config_.synaptics.table_start_block; |
| } |
| |
| for (uint32_t i = 0; i < table_blocks; i++) { |
| const uint32_t block = ((start_block + i) % table_blocks) + config_.synaptics.table_start_block; |
| if (block != except_block && !IsBadBlock(bbt_contents_, block)) { |
| return block; |
| } |
| } |
| |
| return InvalidBlock(); |
| } |
| |
| zx_status_t SynapticsBadBlock::ReadBadBlockTablePattern(uint32_t block, |
| uint8_t out_pattern[kPatternSize], |
| uint8_t* out_version) { |
| zx_status_t status = ReadFirstPage(block); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| uint8_t oob_buffer[kPatternSize + sizeof(*out_version)]; |
| if ((status = oob_vmo_.read(oob_buffer, kTablePatternOffset, sizeof(oob_buffer))) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to read VMO: %d", __func__, status); |
| return status; |
| } |
| |
| memcpy(out_pattern, oob_buffer, kPatternSize); |
| *out_version = oob_buffer[kPatternSize]; |
| return status; |
| } |
| |
| uint32_t SynapticsBadBlock::FindBadBlockTable() { |
| uint32_t table_block = InvalidBlock(); |
| |
| // Find the bad block table with the highest version number. |
| for (uint32_t i = config_.synaptics.table_end_block; i >= config_.synaptics.table_start_block; |
| i--) { |
| uint8_t version; |
| uint8_t pattern[kPatternSize]; |
| if (ReadBadBlockTablePattern(i, pattern, &version) == ZX_OK) { |
| if (memcmp(pattern, kTablePattern, sizeof(pattern)) == 0) { |
| bbt_block_ = i; |
| } else if (memcmp(pattern, kMirrorPattern, sizeof(pattern)) == 0) { |
| bbt_mirror_block_ = i; |
| } else { |
| continue; |
| } |
| |
| if (version > bbt_version_ || !IsBlockValid(table_block)) { |
| table_block = i; |
| bbt_version_ = version; |
| } |
| } |
| } |
| |
| return table_block; |
| } |
| |
| zx_status_t SynapticsBadBlock::ReadBadBlockTable() { |
| uint32_t table_block = FindBadBlockTable(); |
| if (!IsBlockValid(table_block)) { |
| zxlogf(ERROR, "%s: No bad block table found", __func__); |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| const uint32_t bad_block_table_size = nand_info_.num_blocks / kEntriesPerByte; |
| |
| fbl::AllocChecker ac; |
| bbt_contents_.reset(new (&ac) uint8_t[bad_block_table_size], bad_block_table_size); |
| if (!ac.check()) { |
| zxlogf(ERROR, "%s: Failed to allocate memory for bad block table", __func__); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_status_t status = ReadFirstPage(table_block); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = data_vmo_.read(bbt_contents_.data(), 0, bbt_contents_.size())) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to read VMO: %d", __func__, status); |
| } |
| |
| return status; |
| } |
| |
| zx_status_t SynapticsBadBlock::WriteBadBlockTableToVmo() { |
| zx_status_t status = data_vmo_.write(bbt_contents_.data(), 0, bbt_contents_.size()); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to write VMO: %d", __func__, status); |
| return status; |
| } |
| |
| if ((status = oob_vmo_.write(&bbt_version_, kTableVersionOffset, sizeof(bbt_version_))) != |
| ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to write VMO: %d", __func__, status); |
| } |
| |
| return status; |
| } |
| |
| zx_status_t SynapticsBadBlock::WriteBadBlockTable(uint32_t block, uint32_t except_block, |
| const uint8_t pattern[kPatternSize], |
| uint32_t* out_block) { |
| zx_status_t status = oob_vmo_.write(pattern, kTablePatternOffset, kPatternSize); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to write VMO: %d", __func__, status); |
| return status; |
| } |
| |
| *out_block = FindNextGoodTableBlock(block, except_block); |
| |
| while (IsBlockValid(*out_block)) { |
| status = WriteFirstPage(*out_block); |
| if (status == ZX_OK || status == ZX_ERR_IO) { |
| return status; |
| } |
| |
| *out_block = FindNextGoodTableBlock(*out_block, except_block); |
| } |
| |
| return ZX_ERR_IO_DATA_LOSS; |
| } |
| |
| zx_status_t SynapticsBadBlock::ReadFirstPage(uint32_t block) { |
| sync_completion_t completion; |
| BlockOperationContext op_ctx = {.completion_event = &completion, .status = ZX_ERR_INTERNAL}; |
| |
| auto* nand_op = reinterpret_cast<nand_operation_t*>(nand_op_.data()); |
| nand_op->rw.command = NAND_OP_READ; |
| nand_op->rw.data_vmo = data_vmo_.get(); |
| nand_op->rw.oob_vmo = oob_vmo_.get(); |
| nand_op->rw.length = 1; |
| nand_op->rw.offset_nand = block * nand_info_.pages_per_block; |
| nand_op->rw.offset_data_vmo = 0; |
| nand_op->rw.offset_oob_vmo = 0; |
| nand_op->rw.corrected_bit_flips = 0; |
| |
| nand_.Queue(nand_op, CompletionCallback, &op_ctx); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| if (op_ctx.status != ZX_OK) { |
| zxlogf(ERROR, "%s: NAND read failed: %d", __func__, op_ctx.status); |
| } |
| |
| return op_ctx.status; |
| } |
| |
| zx_status_t SynapticsBadBlock::WriteFirstPage(uint32_t block) { |
| sync_completion_t completion; |
| BlockOperationContext op_ctx = {.completion_event = &completion, .status = ZX_ERR_INTERNAL}; |
| |
| auto* nand_op = reinterpret_cast<nand_operation_t*>(nand_op_.data()); |
| |
| nand_op->erase.command = NAND_OP_ERASE; |
| nand_op->erase.first_block = block; |
| nand_op->erase.num_blocks = 1; |
| |
| nand_.Queue(nand_op, CompletionCallback, &op_ctx); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| if (op_ctx.status != ZX_OK) { |
| zxlogf(ERROR, "%s: NAND erase failed: %d", __func__, op_ctx.status); |
| return op_ctx.status; |
| } |
| |
| op_ctx.status = ZX_ERR_INTERNAL; |
| |
| nand_op->rw.command = NAND_OP_WRITE; |
| nand_op->rw.data_vmo = data_vmo_.get(); |
| nand_op->rw.oob_vmo = oob_vmo_.get(); |
| nand_op->rw.length = 1; |
| nand_op->rw.offset_nand = block * nand_info_.pages_per_block; |
| nand_op->rw.offset_data_vmo = 0; |
| nand_op->rw.offset_oob_vmo = 0; |
| nand_op->rw.corrected_bit_flips = 0; |
| |
| nand_.Queue(nand_op, CompletionCallback, &op_ctx); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| if (op_ctx.status != ZX_OK) { |
| zxlogf(ERROR, "%s: NAND write failed: %d", __func__, op_ctx.status); |
| } |
| |
| return op_ctx.status; |
| } |
| |
| } // namespace nand |