| // Copyright 2018 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 <lib/ftl/ndm-driver.h> |
| |
| #include <zircon/assert.h> |
| |
| #include "kprivate/fsdriver.h" |
| #include "kprivate/fsprivate.h" |
| #include "kprivate/ndm.h" |
| #include "posix.h" |
| |
| namespace ftl { |
| |
| namespace { |
| |
| bool g_init_performed = false; |
| |
| // Implementation of the driver interface: |
| |
| // Returns kNdmOk, kNdmUncorrectableEcc, kNdmFatalError or kNdmUnsafeEcc. |
| int ReadPagesImpl(uint32_t page, uint32_t count, uint8_t* data, uint8_t* spare, void* dev) { |
| NdmDriver* device = reinterpret_cast<NdmDriver*>(dev); |
| return device->NandRead(page, count, data, spare); |
| } |
| |
| // Returns kNdmOk, kNdmUncorrectableEcc, kNdmFatalError or kNdmUnsafeEcc. |
| int ReadPages(uint32_t page, uint32_t count, uint8_t* data, uint8_t* spare, void* dev) { |
| return ReadPagesImpl(page, count, data, nullptr, dev); |
| } |
| |
| // Returns kNdmOk, kNdmUncorrectableEcc, kNdmFatalError or kNdmUnsafeEcc. |
| int ReadPage(uint32_t page, uint8_t* data, uint8_t* spare, void* dev) { |
| return ReadPagesImpl(page, 1, data, nullptr, dev); |
| } |
| |
| // Returns kNdmOk or kNdmError on ECC decode failure. |
| int ReadSpare(uint32_t page, uint8_t* spare, void* dev) { |
| int result = ReadPagesImpl(page, 1, nullptr, spare, dev); |
| if (result == kNdmFatalError || result == kNdmUncorrectableEcc) { |
| return kNdmError; |
| } |
| |
| // kNdmUnsafeEcc is also OK as the data is still correct. |
| return kNdmOk; |
| } |
| |
| // Returns kNdmOk or kNdmError. |
| int ReadSpareNoEcc(uint32_t page, uint8_t* spare, void* dev) { |
| int result = ReadPagesImpl(page, 1, nullptr, spare, dev); |
| return result == kNdmFatalError ? kNdmError : kNdmOk; |
| } |
| |
| // Returns kNdmOk, kNdmError or kNdmFatalError. kNdmError triggers marking the block as bad. |
| int WritePages(uint32_t page, uint32_t count, const uint8_t* data, uint8_t* spare, int action, |
| void* dev) { |
| NdmDriver* device = reinterpret_cast<NdmDriver*>(dev); |
| return device->NandWrite(page, count, data, spare); |
| } |
| |
| // Returns kNdmOk, kNdmError or kNdmFatalError. kNdmError triggers marking the block as bad. |
| int WritePage(uint32_t page, const uint8_t* data, uint8_t* spare, int action, void* dev) { |
| return WritePages(page, 1, data, spare, action, dev); |
| } |
| |
| // Returns kNdmOk or kNdmError. kNdmError triggers marking the block as bad. |
| int EraseBlock(uint32_t page, void* dev) { |
| NdmDriver* device = reinterpret_cast<NdmDriver*>(dev); |
| return device->NandErase(page); |
| } |
| |
| // Returns kTrue, kFalse or kNdmError. |
| int IsBadBlockImpl(uint32_t page, void* dev) { |
| NdmDriver* device = reinterpret_cast<NdmDriver*>(dev); |
| return device->IsBadBlock(page); |
| } |
| |
| // Returns kTrue or kFalse (kFalse on error). |
| int IsEmpty(uint32_t page, uint8_t* data, uint8_t* spare, void* dev) { |
| int result = ReadPagesImpl(page, 1, data, spare, dev); |
| |
| // kNdmUncorrectableEcc and kNdmUnsafeEcc are ok. |
| if (result == kNdmFatalError) { |
| return kFalse; |
| } |
| |
| NdmDriver* device = reinterpret_cast<NdmDriver*>(dev); |
| return device->IsEmptyPage(page, data, spare) ? kTrue : kFalse; |
| } |
| |
| // Returns kNdmOk or kNdmError. |
| int CheckPage(uint32_t page, uint8_t* data, uint8_t* spare, int* status, void* dev) { |
| *status = IsEmpty(page, data, spare, dev) ? NDM_PAGE_ERASED : NDM_PAGE_VALID; |
| return kNdmOk; |
| } |
| |
| } // namespace |
| |
| NdmBaseDriver::~NdmBaseDriver() { |
| RemoveNdmVolume(); |
| } |
| |
| bool NdmBaseDriver::IsNdmDataPresent(const VolumeOptions& options) { |
| NDMDrvr driver = {}; |
| driver.num_blocks = options.num_blocks; |
| driver.max_bad_blocks = options.max_bad_blocks; |
| driver.block_size = options.block_size; |
| driver.page_size = options.page_size; |
| driver.eb_size = options.eb_size; |
| driver.flags = FSF_MULTI_ACCESS | FSF_FREE_SPARE_ECC | options.flags; |
| driver.dev = this; |
| driver.type = NDM_SLC; |
| driver.read_pages = ReadPages; |
| driver.write_pages = WritePages; |
| driver.write_data_and_spare = WritePage; |
| driver.read_decode_data = ReadPage; |
| driver.read_decode_spare = ReadSpare; |
| driver.read_spare = ReadSpareNoEcc; |
| driver.data_and_spare_erased = IsEmpty; |
| driver.data_and_spare_check = CheckPage; |
| driver.erase_block = EraseBlock; |
| driver.is_block_bad = IsBadBlockImpl; |
| |
| SetFsErrCode(NDM_OK); |
| ndm_ = ndmAddDev(&driver); |
| return ndm_ || GetFsErrCode() != NDM_NO_META_BLK; |
| } |
| |
| bool NdmBaseDriver::BadBbtReservation() const { |
| if (ndm_) { |
| return false; |
| } |
| FsErrorCode error = static_cast<FsErrorCode>(GetFsErrCode()); |
| switch (error) { |
| case NDM_TOO_MANY_IBAD: |
| case NDM_TOO_MANY_RBAD: |
| case NDM_RBAD_LOCATION: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| const char* NdmBaseDriver::CreateNdmVolume(const Volume* ftl_volume, const VolumeOptions& options) { |
| if (!ndm_) { |
| IsNdmDataPresent(options); |
| } |
| |
| if (!ndm_) { |
| return "ndmAddDev failed"; |
| } |
| |
| if (ndmSetNumPartitions(ndm_, 1) != 0) { |
| return "ndmSetNumPartitions failed"; |
| } |
| |
| NDMPartition partition = {}; |
| partition.num_blocks = ndmGetNumVBlocks(ndm_) - partition.first_block; |
| if (ndmWritePartition(ndm_, &partition, 0, "ftl") != 0) { |
| return "ndmWritePartition failed"; |
| } |
| |
| FtlNdmVol ftl = {}; |
| XfsVol xfs = {}; |
| |
| ftl.flags = FSF_EXTRA_FREE; |
| ftl.cached_map_pages = options.num_blocks * (options.block_size / options.page_size); |
| ftl.extra_free = 6; // Over-provision 6% of the device. |
| xfs.ftl_volume = const_cast<Volume*>(ftl_volume); |
| |
| if (ndmAddVolFTL(ndm_, 0, &ftl, &xfs) != 0) { |
| return "ndmAddVolFTL failed"; |
| } |
| |
| return nullptr; |
| } |
| |
| bool NdmBaseDriver::RemoveNdmVolume() { |
| if (ndm_ && ndmDelDev(ndm_) == 0) { |
| ndm_ = nullptr; |
| return true; |
| } |
| return false; |
| } |
| |
| bool NdmBaseDriver::SaveBadBlockData() { |
| return ndmExtractBBL(ndm_) >= 0 ? true : false; |
| } |
| |
| bool NdmBaseDriver::RestoreBadBlockData() { |
| return ndmInsertBBL(ndm_) == 0 ? true : false; |
| } |
| |
| bool NdmBaseDriver::IsEmptyPageImpl(const uint8_t* data, uint32_t data_len, const uint8_t* spare, |
| uint32_t spare_len) const { |
| const int64_t* pointer = reinterpret_cast<const int64_t*>(data); |
| ZX_DEBUG_ASSERT(data_len % sizeof(*pointer) == 0); |
| for (size_t i = 0; i < data_len / sizeof(*pointer); i++) { |
| if (pointer[i] != -1) { |
| return false; |
| } |
| } |
| |
| ZX_DEBUG_ASSERT(spare_len % sizeof(*pointer) == 0); |
| pointer = reinterpret_cast<const int64_t*>(spare); |
| for (size_t i = 0; i < spare_len / sizeof(*pointer); i++) { |
| if (pointer[i] != -1) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool InitModules() { |
| if (!g_init_performed) { |
| // Unfortunately, module initialization is a global affair, and there is |
| // no cleanup. At least, make sure no re-initialization takes place. |
| if (NdmInit() != 0 || FtlInit() != 0) { |
| return false; |
| } |
| g_init_performed = true; |
| } |
| return true; |
| } |
| |
| } // namespace ftl |