blob: 88807ce59283799ede38c312555fa68d2a71060d [file] [log] [blame]
// 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