| // 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 <fcntl.h> |
| #include <math.h> |
| #include <stdio.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <functional> |
| #include <string> |
| #include <utility> |
| |
| #include <zircon/assert.h> |
| |
| #include <lib/ftl-mtd/nand-volume-driver.h> |
| |
| namespace ftl_mtd { |
| |
| using namespace std::placeholders; |
| |
| zx_status_t NandVolumeDriver::Create(uint32_t block_offset, uint32_t max_bad_blocks, |
| std::unique_ptr<mtd::NandInterface> interface, |
| std::unique_ptr<NandVolumeDriver>* out) { |
| uint32_t block_count = interface->Size() / interface->BlockSize(); |
| if (block_offset >= block_count) { |
| fprintf(stderr, "Block offset must be less than block count of %u.\n", block_count); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| uint32_t usable_block_count = block_count - block_offset; |
| if (max_bad_blocks >= usable_block_count) { |
| fprintf(stderr, "Max bad blocks must be less than block count of %u.\n", |
| usable_block_count); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| uint32_t page_multiplier = 1; |
| while (page_multiplier * interface->OobSize() < kMinimumOobSize) { |
| page_multiplier *= 2; |
| } |
| |
| *out = std::unique_ptr<NandVolumeDriver>( |
| new NandVolumeDriver(block_offset, max_bad_blocks, page_multiplier, std::move(interface))); |
| return ZX_OK; |
| } |
| |
| NandVolumeDriver::NandVolumeDriver(uint32_t block_offset, uint32_t max_bad_blocks, |
| uint32_t page_multiplier, |
| std::unique_ptr<mtd::NandInterface> interface) |
| : block_offset_(block_offset), page_multiplier_(page_multiplier), |
| max_bad_blocks_(max_bad_blocks), interface_(std::move(interface)) {} |
| |
| const char* NandVolumeDriver::Init() { |
| return nullptr; |
| } |
| |
| const char* NandVolumeDriver::Attach(const ftl::Volume* ftl_volume) { |
| ftl::VolumeOptions options = { |
| .num_blocks = (interface_->Size() - ByteOffset()) / interface_->BlockSize(), |
| // This should be 2%, but that is of the whole device, not just this partition. |
| .max_bad_blocks = max_bad_blocks_, |
| .block_size = interface_->BlockSize(), |
| .page_size = MappedPageSize(), |
| .eb_size = MappedOobSize(), |
| .flags = 0 // Same as FSF_DRVR_PAGES (current default). |
| }; |
| |
| return CreateNdmVolume(ftl_volume, options); |
| } |
| |
| bool NandVolumeDriver::Detach() { |
| return RemoveNdmVolume(); |
| } |
| |
| int NandVolumeDriver::NandWrite(uint32_t start_page, uint32_t page_count, const void* page_buffer, |
| const void* oob_buffer) { |
| uint32_t real_start_page; |
| uint32_t real_end_page; |
| if (GetPageIndices(start_page, page_count, &real_start_page, &real_end_page) != ZX_OK) { |
| return ftl::kNdmFatalError; |
| } |
| |
| const uint8_t* page_buffer_ptr = reinterpret_cast<const uint8_t*>(page_buffer); |
| const uint8_t* oob_buffer_ptr = reinterpret_cast<const uint8_t*>(oob_buffer); |
| |
| for (uint32_t page = real_start_page; page < real_end_page; page++) { |
| zx_status_t status = interface_->WritePage( |
| GetByteOffsetForPage(page), page_buffer_ptr, oob_buffer_ptr); |
| |
| // We checked the inputs before, so the interface should never return ZX_ERR_INVALID_ARGS. |
| ZX_ASSERT(status != ZX_ERR_INVALID_ARGS); |
| |
| if (status != ZX_OK) { |
| return ftl::kNdmError; |
| } |
| |
| page_buffer_ptr += interface_->PageSize(); |
| oob_buffer_ptr += interface_->OobSize(); |
| } |
| |
| return ftl::kNdmOk; |
| } |
| |
| int NandVolumeDriver::NandRead(uint32_t start_page, uint32_t page_count, void* page_buffer, |
| void* oob_buffer) { |
| uint32_t real_start_page; |
| uint32_t real_end_page; |
| if (GetPageIndices(start_page, page_count, &real_start_page, &real_end_page) != ZX_OK) { |
| return ftl::kNdmFatalError; |
| } |
| |
| uint8_t* page_buffer_ptr = reinterpret_cast<uint8_t*>(page_buffer); |
| uint8_t* oob_buffer_ptr = reinterpret_cast<uint8_t*>(oob_buffer); |
| |
| for (uint32_t page = real_start_page; page < real_end_page; page++) { |
| zx_status_t status = ReadPageAndOob( |
| GetByteOffsetForPage(page), page_buffer_ptr, oob_buffer_ptr); |
| |
| if (status != ZX_OK) { |
| return ftl::kNdmFatalError; |
| } |
| |
| if (page_buffer) { |
| page_buffer_ptr += interface_->PageSize(); |
| } |
| |
| if (oob_buffer) { |
| oob_buffer_ptr += interface_->OobSize(); |
| } |
| } |
| |
| return ftl::kNdmOk; |
| } |
| |
| int NandVolumeDriver::NandErase(uint32_t page_num) { |
| uint32_t real_start_page; |
| uint32_t real_end_page; |
| if (GetPageIndices(page_num, 1, &real_start_page, &real_end_page) != ZX_OK) { |
| return ftl::kNdmError; |
| } |
| |
| zx_status_t status = interface_->EraseBlock(GetBlockOffsetForPage(real_start_page)); |
| return status == ZX_OK ? ftl::kNdmOk : ftl::kNdmError; |
| } |
| |
| int NandVolumeDriver::IsBadBlock(uint32_t page_num) { |
| bool is_bad_block = false; |
| |
| uint32_t real_start_page; |
| uint32_t real_end_page; |
| if (GetPageIndices(page_num, 1, &real_start_page, &real_end_page) != ZX_OK) { |
| return ftl::kNdmError; |
| } |
| |
| zx_status_t status = interface_->IsBadBlock(GetBlockOffsetForPage(real_start_page), |
| &is_bad_block); |
| if (status != ZX_OK) { |
| return ftl::kNdmError; |
| } |
| |
| return is_bad_block ? ftl::kTrue : ftl::kFalse; |
| } |
| |
| bool NandVolumeDriver::IsEmptyPage(uint32_t page_num, const uint8_t* page_buffer, |
| const uint8_t* oob_buffer) { |
| return IsEmptyPageImpl(page_buffer, MappedPageSize(), oob_buffer, MappedOobSize()); |
| } |
| |
| zx_status_t NandVolumeDriver::ReadPageAndOob(uint32_t byte_offset, void* page_buffer, |
| void* oob_buffer) { |
| if (page_buffer) { |
| uint32_t actual; |
| zx_status_t read_page_status = interface_->ReadPage(byte_offset, page_buffer, &actual); |
| |
| if (read_page_status != ZX_OK) { |
| return read_page_status; |
| } |
| |
| if (actual != interface_->PageSize()) { |
| return ZX_ERR_IO_DATA_LOSS; |
| } |
| } |
| |
| if (oob_buffer) { |
| zx_status_t read_oob_status = interface_->ReadOob(byte_offset, oob_buffer); |
| if (read_oob_status != ZX_OK) { |
| return read_oob_status; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t NandVolumeDriver::GetPageIndices(uint32_t mapped_page, uint32_t mapped_page_count, |
| uint32_t* start_page, uint32_t* end_page) { |
| uint32_t start = ByteOffset() / interface_->PageSize() + page_multiplier_ * mapped_page; |
| uint32_t end = start + page_multiplier_ * mapped_page_count; |
| uint32_t last_page = interface_->Size() / interface_->PageSize(); |
| |
| if (start >= last_page || end > last_page) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| *start_page = start; |
| *end_page = end; |
| return ZX_OK; |
| } |
| |
| uint32_t NandVolumeDriver::GetBlockOffsetForPage(uint32_t real_page) { |
| return GetByteOffsetForPage(real_page) / interface_->BlockSize() * interface_->BlockSize(); |
| } |
| |
| uint32_t NandVolumeDriver::GetByteOffsetForPage(uint32_t real_page) { |
| return real_page * interface_->PageSize(); |
| } |
| |
| uint32_t NandVolumeDriver::ByteOffset() { |
| return block_offset_ * interface_->BlockSize(); |
| } |
| |
| uint32_t NandVolumeDriver::MappedPageSize() { |
| return page_multiplier_ * interface_->PageSize(); |
| } |
| |
| uint32_t NandVolumeDriver::MappedOobSize() { |
| return page_multiplier_ * interface_->OobSize(); |
| } |
| |
| } // namespace ftl_mtd |