| // 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 <stdarg.h> |
| #include <zircon/assert.h> |
| |
| #include <memory> |
| #include <optional> |
| |
| #include "ftl.h" |
| #include "ftl_private.h" |
| #include "ndm/ndmp.h" |
| |
| namespace ftl { |
| |
| namespace { |
| |
| bool g_init_performed = false; |
| |
| // Extra configuration data saved to the partition info. |
| struct UserData { |
| uint16_t major_version = 1; |
| uint16_t minor_version = 0; |
| uint32_t ftl_flags; // Flags used to create the FtlNdmVol structure. |
| uint32_t extra_free; // Overallocation for the FTL. |
| uint32_t reserved_1[5]; |
| VolumeOptions options; |
| uint32_t reserved_2[10]; |
| }; |
| static_assert(sizeof(UserData) == 96); |
| |
| // This structure exposes the two views into the partition data. |
| // See ftl.h for the definition of NDMPartitionInfo. |
| union PartitionInfo { |
| NDMPartitionInfo ndm; |
| struct { |
| // This is the equivalent structure, with an explicit |data| field of the |
| // "correct" type, instead of just a placeholder. In this case, |data_size| |
| // tracks |data|. |
| NDMPartition basic_data; |
| uint32_t data_size = sizeof(UserData); |
| UserData data; |
| } exploded; |
| }; |
| static_assert(sizeof(NDMPartition) + sizeof(uint32_t) == sizeof(NDMPartitionInfo)); |
| static_assert(sizeof(NDMPartitionInfo) + sizeof(UserData) == sizeof(PartitionInfo)); |
| |
| // Fills |data| with the desired configuration info. |
| void CopyConfigData(const VolumeOptions& options, const FtlNdmVol& ftl, UserData* data) { |
| data->ftl_flags = ftl.flags; |
| data->extra_free = ftl.extra_free; |
| data->options = options; |
| } |
| |
| // 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, but kNdmError implies aborting initialization. |
| int CheckPage(uint32_t page, uint8_t* data, uint8_t* spare, int* status, void* dev) { |
| int result = ReadPagesImpl(page, 1, data, spare, dev); |
| |
| if (result == kNdmUncorrectableEcc || result == kNdmFatalError) { |
| *status = NDM_PAGE_INVALID; |
| return kNdmOk; |
| } |
| |
| NdmDriver* device = reinterpret_cast<NdmDriver*>(dev); |
| bool empty = device->IsEmptyPage(page, data, spare) ? kTrue : kFalse; |
| |
| *status = empty ? NDM_PAGE_ERASED : NDM_PAGE_VALID; |
| return kNdmOk; |
| } |
| |
| __PRINTFLIKE(3, 4) void LogTrace(const char* file, int line, const char* format, ...) { |
| fprintf(stderr, "[FTL] TRACE: "); |
| va_list args; |
| va_start(args, format); |
| vfprintf(stderr, format, args); |
| va_end(args); |
| fprintf(stderr, "\n"); |
| } |
| |
| __PRINTFLIKE(3, 4) void LogDebug(const char* file, int line, const char* format, ...) { |
| fprintf(stderr, "[FTL] DEBUG: "); |
| va_list args; |
| va_start(args, format); |
| vfprintf(stderr, format, args); |
| va_end(args); |
| fprintf(stderr, "\n"); |
| } |
| |
| __PRINTFLIKE(3, 4) void LogInfo(const char* file, int line, const char* format, ...) { |
| fprintf(stderr, "[FTL] INFO: "); |
| va_list args; |
| va_start(args, format); |
| vfprintf(stderr, format, args); |
| va_end(args); |
| fprintf(stderr, "\n"); |
| } |
| |
| __PRINTFLIKE(3, 4) void LogWarning(const char* file, int line, const char* format, ...) { |
| fprintf(stderr, "[FTL] WARNING: "); |
| va_list args; |
| va_start(args, format); |
| vfprintf(stderr, format, args); |
| va_end(args); |
| fprintf(stderr, "\n"); |
| } |
| |
| __PRINTFLIKE(3, 4) void LogError(const char* file, int line, const char* format, ...) { |
| fprintf(stderr, "[FTL] ERROR: "); |
| va_list args; |
| va_start(args, format); |
| vfprintf(stderr, format, args); |
| va_end(args); |
| fprintf(stderr, "\n"); |
| } |
| |
| Logger GetDefaultLogger() { |
| Logger logger = { |
| .trace = &LogTrace, |
| .debug = &LogDebug, |
| .info = &LogInfo, |
| .warning = &LogWarning, |
| .error = &LogError, |
| }; |
| |
| return logger; |
| } |
| |
| } // namespace |
| |
| NdmBaseDriver::~NdmBaseDriver() { RemoveNdmVolume(); } |
| |
| bool NdmBaseDriver::IsNdmDataPresent(const VolumeOptions& options, bool use_format_v2) { |
| NDMDrvr driver; |
| FillNdmDriver(options, use_format_v2, &driver); |
| |
| 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, |
| bool save_volume_data) { |
| return CreateNdmVolumeWithLogger(ftl_volume, options, save_volume_data, std::nullopt); |
| } |
| |
| const char* NdmBaseDriver::CreateNdmVolumeWithLogger(const Volume* ftl_volume, |
| const VolumeOptions& options, |
| bool save_volume_data, |
| std::optional<LoggerProxy> logger) { |
| logger_ = logger; |
| if (!ndm_) { |
| IsNdmDataPresent(options, save_volume_data); |
| } |
| |
| if (!ndm_) { |
| return "ndmAddDev failed"; |
| } |
| |
| PartitionInfo partition = {}; |
| partition.exploded = {}; // Initialize the "real" structure. |
| 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); |
| ftl.logger = ndm_->logger; |
| |
| partition.exploded.basic_data.num_blocks = ndmGetNumVBlocks(ndm_); |
| strncpy(partition.exploded.basic_data.name, "ftl", sizeof(partition.exploded.basic_data.name)); |
| CopyConfigData(options, ftl, &partition.exploded.data); |
| |
| if (save_volume_data) { |
| const NDMPartitionInfo* info = ndmGetPartitionInfo(ndm_); |
| if (info) { |
| volume_data_saved_ = true; |
| } |
| |
| if (ndmWritePartitionInfo(ndm_, &partition.ndm) != 0) { |
| return "ndmWritePartitionInfo failed"; |
| } |
| |
| if (!info && !(options.flags & kReadOnlyInit)) { |
| // There was no volume information saved, save it now. |
| if (ndmSavePartitionTable(ndm_) != 0) { |
| return "ndmSavePartitionTable failed"; |
| } |
| volume_data_saved_ = true; |
| } |
| } else { |
| // This call also allocates the partition data, but old style. |
| if (ndmSetNumPartitions(ndm_, 1) != 0) { |
| return "ndmSetNumPartitions failed"; |
| } |
| |
| if (ndmWritePartition(ndm_, &partition.ndm.basic_data, 0, "ftl") != 0) { |
| return "ndmWritePartition failed"; |
| } |
| } |
| |
| if (ndmAddVolFTL(ndm_, 0, &ftl, &xfs) == NULL) { |
| 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; |
| } |
| |
| const VolumeOptions* NdmBaseDriver::GetSavedOptions() const { |
| const NDMPartitionInfo* partition = ndmGetPartitionInfo(ndm_); |
| if (!partition) { |
| return nullptr; |
| } |
| |
| auto info = reinterpret_cast<const PartitionInfo*>(partition); |
| if (info->exploded.data_size != sizeof(UserData)) { |
| return nullptr; |
| } |
| |
| if (info->exploded.data.major_version != 1) { |
| return nullptr; |
| } |
| |
| return &info->exploded.data.options; |
| } |
| |
| bool NdmBaseDriver::WriteVolumeData() { |
| if (ndmSavePartitionTable(ndm_) != 0) { |
| return false; |
| } |
| volume_data_saved_ = true; |
| return true; |
| } |
| |
| void NdmBaseDriver::FillNdmDriver(const VolumeOptions& options, bool use_format_v2, |
| NDMDrvr* driver) const { |
| *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->format_version_2 = use_format_v2; |
| driver->dev = const_cast<NdmBaseDriver*>(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; |
| driver->logger = GetDefaultLogger(); |
| |
| if (logger_.has_value()) { |
| if (logger_->trace != nullptr) { |
| driver->logger.trace = logger_->trace; |
| } |
| |
| if (logger_->debug != nullptr) { |
| driver->logger.debug = logger_->debug; |
| } |
| |
| if (logger_->info != nullptr) { |
| driver->logger.info = logger_->info; |
| } |
| |
| if (logger_->warn != nullptr) { |
| driver->logger.warning = logger_->warn; |
| } |
| |
| if (logger_->error != nullptr) { |
| driver->logger.error = logger_->error; |
| } |
| } |
| } |
| |
| __EXPORT |
| 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 |