blob: 507bd12d1a9957f1bea8df710a7f6a6004de82c7 [file] [log] [blame]
// 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 <ftl_private.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