blob: 2f42106c3be691db3fd30bf235d40a83e5982a13 [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 <stdarg.h>
#include <zircon/assert.h>
#include <memory>
#include <optional>
#include "ftl.h"
#include "ftl_private.h"
#include "ftlnp.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);
for (uint32_t i = 0; i < count; i++) {
FtlnSetSpareValidity(&spare[i * device->SpareSize()], &data[i * device->PageSize()],
device->PageSize());
}
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);
NdmDriver* device = reinterpret_cast<NdmDriver*>(dev);
if (result == kNdmUncorrectableEcc || result == kNdmFatalError ||
device->IncompletePageWrite(spare, data)) {
*status = NDM_PAGE_INVALID;
return kNdmOk;
}
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");
}
} // namespace
FtlLogger DefaultLogger() {
return FtlLogger{
.trace = &LogTrace,
.debug = &LogDebug,
.info = &LogInfo,
.warn = &LogWarning,
.error = &LogError,
};
}
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) {
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 = 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 = logger_;
}
uint32_t NdmBaseDriver::PageSize() { return ndm_->page_size; }
uint8_t NdmBaseDriver::SpareSize() { return ndm_->eb_size; }
bool NdmBaseDriver::IncompletePageWrite(uint8_t* spare, uint8_t* data) {
return FtlnIncompleteWrite(spare, data, PageSize());
}
__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