blob: f9839394ba67c4d6648e048e672b953f8925beff [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 "src/devices/nand/drivers/skip-block/skip-block.h"
#include <lib/fzl/vmo-mapper.h>
#include <lib/sync/completion.h>
#include <lib/zx/vmo.h>
#include <string.h>
#include <zircon/boot/image.h>
#include <zircon/status.h>
#include <algorithm>
#include <memory>
#include <utility>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/metadata.h>
#include <ddk/protocol/badblock.h>
#include <ddk/protocol/nand.h>
#include <ddktl/fidl.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include "src/devices/nand/drivers/skip-block/skip-block-bind.h"
namespace nand {
namespace {
struct BlockOperationContext {
ReadWriteOperation op;
fuchsia_hardware_nand_Info* nand_info;
LogicalToPhysicalMap* block_map;
ddk::NandProtocolClient* nand;
uint32_t copy;
uint32_t current_block;
uint32_t physical_block;
sync_completion_t* completion_event;
zx_status_t status;
bool mark_bad;
std::optional<PageRange> write_page_range;
};
// Called when all page reads in a block finish. If another block still needs
// to be read, it queues it up as another operation.
void ReadCompletionCallback(void* cookie, zx_status_t status, nand_operation_t* op) {
auto* ctx = static_cast<BlockOperationContext*>(cookie);
if (status != ZX_OK || ctx->current_block + 1 == ctx->op.block + ctx->op.block_count) {
ctx->status = status;
ctx->mark_bad = false;
sync_completion_signal(ctx->completion_event);
return;
}
ctx->current_block += 1;
status = ctx->block_map->GetPhysical(ctx->copy, ctx->current_block, &ctx->physical_block);
if (status != ZX_OK) {
ctx->status = status;
ctx->mark_bad = false;
sync_completion_signal(ctx->completion_event);
return;
}
op->rw.offset_nand = ctx->physical_block * ctx->nand_info->pages_per_block;
op->rw.offset_data_vmo += ctx->nand_info->pages_per_block;
ctx->nand->Queue(op, ReadCompletionCallback, cookie);
return;
}
void EraseCompletionCallback(void* cookie, zx_status_t status, nand_operation_t* op);
// Called when all page writes in a block finish. If another block still needs
// to be written, it queues up an erase.
void WriteCompletionCallback(void* cookie, zx_status_t status, nand_operation_t* op) {
auto* ctx = static_cast<BlockOperationContext*>(cookie);
if (status != ZX_OK || ctx->current_block + 1 == ctx->op.block + ctx->op.block_count) {
ctx->status = status;
ctx->mark_bad = (status == ZX_ERR_IO);
sync_completion_signal(ctx->completion_event);
return;
}
ctx->current_block += 1;
status = ctx->block_map->GetPhysical(ctx->copy, ctx->current_block, &ctx->physical_block);
if (status != ZX_OK) {
ctx->status = status;
ctx->mark_bad = false;
sync_completion_signal(ctx->completion_event);
return;
}
op->erase.command = NAND_OP_ERASE;
op->erase.first_block = ctx->physical_block;
op->erase.num_blocks = 1;
ctx->nand->Queue(op, EraseCompletionCallback, cookie);
return;
}
// Given a block specified in |ctx| and the target page range to write to specified by |page_range|,
// compute the write range within this block and the offset into input vmo.
void ComputeInBlockWriteRangeFromPageRange(const BlockOperationContext* ctx,
const PageRange& page_range, size_t* in_block_offset,
size_t* write_size, size_t* vmo_offset) {
// All following quantities are logical page indices relative to logical page 0 of the storage.
// Page index of the current block
const size_t block_start_page = ctx->current_block * ctx->nand_info->pages_per_block;
// Page index of the end of current block
const size_t block_end_page = block_start_page + ctx->nand_info->pages_per_block;
// Page index where the write within the current block should start.
const size_t write_start_page = std::max(block_start_page, page_range.page_offset);
// Page index where the write within the current block should end.
const size_t write_end_page =
std::min(block_end_page, page_range.page_offset + page_range.page_count);
// |write_start_page - page_range.page_offset| is essentially the number of pages we have
// written so far.
*vmo_offset = ctx->op.vmo_offset + write_start_page - page_range.page_offset;
*in_block_offset = write_start_page - block_start_page;
*write_size = write_end_page - write_start_page;
}
// Called when a block erase operation finishes. Subsequently queues up writes
// to the block.
void EraseCompletionCallback(void* cookie, zx_status_t status, nand_operation_t* op) {
auto* ctx = static_cast<BlockOperationContext*>(cookie);
if (status != ZX_OK) {
ctx->status = status;
ctx->mark_bad = (status == ZX_ERR_IO);
sync_completion_signal(ctx->completion_event);
return;
}
size_t in_block_offset = 0;
size_t write_size = ctx->nand_info->pages_per_block;
size_t vmo_offset =
ctx->op.vmo_offset + ((ctx->current_block - ctx->op.block) * ctx->nand_info->pages_per_block);
if (ctx->write_page_range) {
ComputeInBlockWriteRangeFromPageRange(ctx, *(ctx->write_page_range), &in_block_offset,
&write_size, &vmo_offset);
}
const size_t offset_nand =
ctx->physical_block * ctx->nand_info->pages_per_block + in_block_offset;
ZX_ASSERT(write_size <= UINT32_MAX);
ZX_ASSERT(offset_nand <= UINT32_MAX);
op->rw.command = NAND_OP_WRITE;
op->rw.data_vmo = ctx->op.vmo.get();
op->rw.oob_vmo = ZX_HANDLE_INVALID;
op->rw.length = static_cast<uint32_t>(write_size);
op->rw.offset_nand = static_cast<uint32_t>(offset_nand);
op->rw.offset_data_vmo = vmo_offset;
ctx->nand->Queue(op, WriteCompletionCallback, cookie);
return;
}
} // namespace
zx_status_t SkipBlockDevice::Create(void*, zx_device_t* parent) {
// Get NAND protocol.
ddk::NandProtocolClient nand(parent);
if (!nand.is_valid()) {
zxlogf(ERROR, "skip-block: parent device '%s': does not support nand protocol",
device_get_name(parent));
return ZX_ERR_NOT_SUPPORTED;
}
// Get bad block protocol.
ddk::BadBlockProtocolClient bad_block(parent);
if (!bad_block.is_valid()) {
zxlogf(ERROR, "skip-block: parent device '%s': does not support bad_block protocol",
device_get_name(parent));
return ZX_ERR_NOT_SUPPORTED;
}
uint32_t copy_count;
size_t actual;
zx_status_t status = device_get_metadata(parent, DEVICE_METADATA_PRIVATE, &copy_count,
sizeof(copy_count), &actual);
if (status != ZX_OK) {
zxlogf(ERROR, "skip-block: parent device '%s' has no private metadata",
device_get_name(parent));
return status;
}
if (actual != sizeof(copy_count)) {
zxlogf(ERROR, "skip-block: Private metadata is of size %zu, expected to be %zu", actual,
sizeof(copy_count));
return ZX_ERR_INTERNAL;
}
fbl::AllocChecker ac;
std::unique_ptr<SkipBlockDevice> device(new (&ac)
SkipBlockDevice(parent, nand, bad_block, copy_count));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
status = device->Bind();
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the device.
__UNUSED auto* dummy = device.release();
return ZX_OK;
}
zx_status_t SkipBlockDevice::GetBadBlockList(fbl::Array<uint32_t>* bad_blocks) {
size_t bad_block_count;
zx_status_t status = bad_block_.GetBadBlockList(nullptr, 0, &bad_block_count);
if (status != ZX_OK) {
return status;
}
if (bad_block_count == 0) {
bad_blocks->reset();
return ZX_OK;
}
const size_t bad_block_list_len = bad_block_count;
std::unique_ptr<uint32_t[]> bad_block_list(new uint32_t[bad_block_count]);
memset(bad_block_list.get(), 0, sizeof(uint32_t) * bad_block_count);
status = bad_block_.GetBadBlockList(bad_block_list.get(), bad_block_list_len, &bad_block_count);
if (status != ZX_OK) {
return status;
}
if (bad_block_list_len != bad_block_count) {
return ZX_ERR_INTERNAL;
}
*bad_blocks = fbl::Array<uint32_t>(bad_block_list.release(), bad_block_count);
return ZX_OK;
}
zx_status_t SkipBlockDevice::Bind() {
zxlogf(INFO, "skip-block: Binding to %s", device_get_name(parent()));
fbl::AutoLock al(&lock_);
if (sizeof(nand_operation_t) > parent_op_size_) {
zxlogf(ERROR, "skip-block: parent op size, %zu, is smaller than minimum op size: %zu",
parent_op_size_, sizeof(nand_operation_t));
return ZX_ERR_INTERNAL;
}
nand_op_ = NandOperation::Alloc(parent_op_size_);
if (!nand_op_) {
return ZX_ERR_NO_MEMORY;
}
// TODO(surajmalhotra): Potentially make this lazy instead of in the bind.
fbl::Array<uint32_t> bad_blocks;
const zx_status_t status = GetBadBlockList(&bad_blocks);
if (status != ZX_OK) {
zxlogf(ERROR, "skip-block: Failed to get bad block list");
return status;
}
block_map_ = LogicalToPhysicalMap(copy_count_, nand_info_.num_blocks, std::move(bad_blocks));
return DdkAdd("skip-block");
}
void SkipBlockDevice::GetPartitionInfo(GetPartitionInfoCompleter::Sync& completer) {
fbl::AutoLock al(&lock_);
PartitionInfo info;
info.block_size_bytes = GetBlockSize();
info.partition_block_count = GetBlockCountLocked();
memcpy(info.partition_guid.data(), nand_info_.partition_guid, ZBI_PARTITION_GUID_LEN);
completer.Reply(ZX_OK, info);
}
zx_status_t SkipBlockDevice::ValidateOperationLocked(const ReadWriteOperation& op) const {
if (op.block_count == 0) {
return ZX_ERR_INVALID_ARGS;
}
if (op.block + op.block_count > GetBlockCountLocked()) {
return ZX_ERR_OUT_OF_RANGE;
}
uint64_t vmo_size;
zx_status_t status = op.vmo.get_size(&vmo_size);
if (status != ZX_OK) {
return ZX_ERR_INVALID_ARGS;
}
if (vmo_size < op.vmo_offset + op.block_count * GetBlockSize()) {
return ZX_ERR_OUT_OF_RANGE;
}
return ZX_OK;
}
zx_status_t SkipBlockDevice::ValidateOperationLocked(const WriteBytesOperation& op) const {
if (op.size == 0) {
return ZX_ERR_INVALID_ARGS;
}
if (op.offset % nand_info_.page_size != 0 || op.size % nand_info_.page_size != 0) {
return ZX_ERR_INVALID_ARGS;
}
if (fbl::round_up(op.offset + op.size, GetBlockSize()) > GetBlockCountLocked() * GetBlockSize()) {
return ZX_ERR_OUT_OF_RANGE;
}
uint64_t vmo_size;
zx_status_t status = op.vmo.get_size(&vmo_size);
if (status != ZX_OK) {
return ZX_ERR_INVALID_ARGS;
}
if (vmo_size < op.vmo_offset + op.size) {
return ZX_ERR_OUT_OF_RANGE;
}
return ZX_OK;
}
zx_status_t SkipBlockDevice::ReadLocked(ReadWriteOperation op) {
for (uint32_t copy = 0; copy < copy_count_; copy++) {
if (block_map_.AvailableBlockCount(copy) < op.block_count) {
zxlogf(INFO, "skipblock: copy %u too small, skipping read attempt.", copy);
continue;
}
uint32_t physical_block;
zx_status_t status = block_map_.GetPhysical(copy, op.block, &physical_block);
if (status != ZX_OK) {
return status;
}
sync_completion_t completion;
BlockOperationContext op_context = {
.op = std::move(op),
.nand_info = &nand_info_,
.block_map = &block_map_,
.nand = &nand_,
.copy = copy,
.current_block = op.block,
.physical_block = physical_block,
.completion_event = &completion,
.status = ZX_OK,
.mark_bad = false,
};
nand_operation_t* nand_op = nand_op_->operation();
nand_op->rw.command = NAND_OP_READ;
nand_op->rw.data_vmo = op_context.op.vmo.get();
nand_op->rw.oob_vmo = ZX_HANDLE_INVALID;
nand_op->rw.length = nand_info_.pages_per_block;
nand_op->rw.offset_nand = physical_block * nand_info_.pages_per_block;
nand_op->rw.offset_data_vmo = op.vmo_offset;
// The read callback will enqueue subsequent reads.
nand_.Queue(nand_op, ReadCompletionCallback, &op_context);
// Wait on completion.
sync_completion_wait(&completion, ZX_TIME_INFINITE);
op = std::move(op_context.op);
if (op_context.status == ZX_OK) {
if (copy != 0) {
zxlogf(INFO, "skipblock: Successfully read block %d, copy %d", op_context.current_block,
copy);
}
return ZX_OK;
}
zxlogf(WARNING, "skipblock: Failed to read block %d, copy %d, with status %s",
op_context.current_block, copy, zx_status_get_string(op_context.status));
}
zxlogf(ERROR, "skipblock: Failed to read any copies of block %d", op.block);
return ZX_ERR_IO;
}
void SkipBlockDevice::Read(ReadWriteOperation op, ReadCompleter::Sync& completer) {
fbl::AutoLock al(&lock_);
zx_status_t status = ValidateOperationLocked(op);
if (status != ZX_OK) {
completer.Reply(status);
return;
}
completer.Reply(ReadLocked(std::move(op)));
}
zx_status_t SkipBlockDevice::WriteLocked(ReadWriteOperation op, bool* bad_block_grown,
std::optional<PageRange> write_page_range) {
*bad_block_grown = false;
bool one_copy_succeeded = false;
for (uint32_t copy = 0; copy < copy_count_; copy++) {
for (;;) {
if (op.block >= block_map_.AvailableBlockCount(copy)) {
break;
}
uint32_t physical_block;
zx_status_t status = block_map_.GetPhysical(copy, op.block, &physical_block);
if (status != ZX_OK) {
return status;
}
sync_completion_t completion;
BlockOperationContext op_context = {
.op = std::move(op),
.nand_info = &nand_info_,
.block_map = &block_map_,
.nand = &nand_,
.copy = copy,
.current_block = op.block,
.physical_block = physical_block,
.completion_event = &completion,
.status = ZX_OK,
.mark_bad = false,
.write_page_range = write_page_range,
};
nand_operation_t* nand_op = nand_op_->operation();
nand_op->erase.command = NAND_OP_ERASE;
nand_op->erase.first_block = physical_block;
nand_op->erase.num_blocks = 1;
// The erase callback will enqueue subsequent writes and erases.
nand_.Queue(nand_op, EraseCompletionCallback, &op_context);
// Wait on completion.
sync_completion_wait(&completion, ZX_TIME_INFINITE);
op = std::move(op_context.op);
if (op_context.mark_bad) {
zxlogf(ERROR, "Failed to erase/write block %u, marking bad", op_context.physical_block);
status = bad_block_.MarkBlockBad(op_context.physical_block);
if (status != ZX_OK) {
zxlogf(ERROR, "skip-block: Failed to mark block bad");
return status;
}
// Logical to physical mapping has changed, so we need to re-initialize block_map_.
fbl::Array<uint32_t> bad_blocks;
// TODO(surajmalhotra): Make it impossible for this to fail.
ZX_ASSERT(GetBadBlockList(&bad_blocks) == ZX_OK);
block_map_ =
LogicalToPhysicalMap(copy_count_, nand_info_.num_blocks, std::move(bad_blocks));
*bad_block_grown = true;
continue;
}
if (op_context.status != ZX_OK) {
zxlogf(ERROR, "Failed to write block %d, copy %d with status %s", op_context.current_block,
copy, zx_status_get_string(op_context.status));
break;
}
one_copy_succeeded = true;
break;
}
}
if (!one_copy_succeeded) {
return ZX_ERR_IO;
}
return ZX_OK;
}
void SkipBlockDevice::Write(ReadWriteOperation op, WriteCompleter::Sync& completer) {
fbl::AutoLock al(&lock_);
bool bad_block_grown = false;
zx_status_t status = ValidateOperationLocked(op);
if (status != ZX_OK) {
completer.Reply(status, bad_block_grown);
return;
}
status = WriteLocked(std::move(op), &bad_block_grown, std::nullopt);
completer.Reply(status, bad_block_grown);
}
zx_status_t SkipBlockDevice::ReadPartialBlocksLocked(WriteBytesOperation op, uint64_t block_size,
uint64_t first_block, uint64_t last_block,
uint64_t op_size, zx::vmo* vmo) {
zx_status_t status = zx::vmo::create(op_size, 0, vmo);
if (status != ZX_OK) {
return status;
}
if (op.offset % block_size) {
// Need to read first block.
zx::vmo dup;
status = vmo->duplicate(ZX_RIGHT_SAME_RIGHTS, &dup);
if (status != ZX_OK) {
return status;
}
ReadWriteOperation rw_op = {
.vmo = std::move(dup),
.vmo_offset = 0,
.block = static_cast<uint32_t>(first_block),
.block_count = 1,
};
status = ReadLocked(std::move(rw_op));
if (status != ZX_OK) {
return status;
}
}
if ((first_block != last_block || op.offset % block_size == 0) &&
(op.offset + op.size) % block_size != 0) {
// Need to read last block.
zx::vmo dup;
status = vmo->duplicate(ZX_RIGHT_SAME_RIGHTS, &dup);
if (status != ZX_OK) {
return status;
}
ReadWriteOperation rw_op = {
.vmo = std::move(dup),
.vmo_offset = last_block * block_size,
.block = static_cast<uint32_t>(last_block),
.block_count = 1,
};
status = ReadLocked(std::move(rw_op));
if (status != ZX_OK) {
return status;
}
}
// Copy from input vmo to newly created one.
fzl::VmoMapper mapper;
const size_t vmo_page_offset = op.vmo_offset % ZX_PAGE_SIZE;
status = mapper.Map(op.vmo, fbl::round_down(op.vmo_offset, ZX_PAGE_SIZE),
fbl::round_up(vmo_page_offset + op.size, ZX_PAGE_SIZE), ZX_VM_PERM_READ);
if (status != ZX_OK) {
return status;
}
status = vmo->write(static_cast<uint8_t*>(mapper.start()) + vmo_page_offset,
op.offset % block_size, op.size);
if (status != ZX_OK) {
return status;
}
return ZX_OK;
}
void SkipBlockDevice::WriteBytes(WriteBytesOperation op, WriteBytesCompleter::Sync& completer) {
fbl::AutoLock al(&lock_);
bool bad_block_grown = false;
zx_status_t status = ValidateOperationLocked(op);
if (status != ZX_OK) {
completer.Reply(status, bad_block_grown);
return;
}
const uint64_t block_size = GetBlockSize();
const uint64_t first_block = op.offset / block_size;
const uint64_t last_block = fbl::round_up(op.offset + op.size, block_size) / block_size - 1;
const uint64_t op_size = (last_block - first_block + 1) * block_size;
if (op.mode == WriteBytesMode::READ_MODIFY_ERASE_WRITE) {
zx::vmo vmo;
if (op_size == op.size) {
// No copies are necessary as offset and size are block aligned.
vmo = std::move(op.vmo);
} else {
status = ReadPartialBlocksLocked(std::move(op), block_size, first_block, last_block, op_size,
&vmo);
if (status != ZX_OK) {
completer.Reply(status, bad_block_grown);
return;
}
}
// Now issue normal write.
ReadWriteOperation rw_op = {
.vmo = std::move(vmo),
.vmo_offset = 0,
.block = static_cast<uint32_t>(first_block),
.block_count = static_cast<uint32_t>(last_block - first_block + 1),
};
status = WriteLocked(std::move(rw_op), &bad_block_grown, {});
} else if (op.mode == WriteBytesMode::ERASE_WRITE) {
// No partial read is necessary
ReadWriteOperation rw_op = {
.vmo = std::move(op.vmo),
.vmo_offset = 0,
.block = static_cast<uint32_t>(first_block),
.block_count = static_cast<uint32_t>(last_block - first_block + 1),
};
auto page_size = nand_info_.page_size;
PageRange page_range{op.offset / page_size, op.size / page_size};
status = WriteLocked(std::move(rw_op), &bad_block_grown, page_range);
}
completer.Reply(status, bad_block_grown);
}
void SkipBlockDevice::WriteBytesWithoutErase(WriteBytesOperation op,
WriteBytesWithoutEraseCompleter::Sync& completer) {
fbl::AutoLock al(&lock_);
if (auto status = ValidateOperationLocked(op); status != ZX_OK) {
zxlogf(INFO, "skipblock: Operation param is invalid.\n");
completer.Reply(status);
return;
}
if (op.vmo_offset % nand_info_.page_size) {
zxlogf(INFO, "skipblock: vmo_offset has to be page aligned for writing without erase");
completer.Reply(ZX_ERR_INVALID_ARGS);
return;
}
const size_t page_offset = op.offset / nand_info_.page_size;
const size_t page_count = op.size / nand_info_.page_size;
const uint64_t block_size = GetBlockSize();
const uint64_t first_block = op.offset / block_size;
const uint64_t last_block = fbl::round_up(op.offset + op.size, block_size) / block_size - 1;
ReadWriteOperation rw_op = {
.vmo = std::move(op.vmo),
.vmo_offset = op.vmo_offset / nand_info_.page_size,
.block = static_cast<uint32_t>(first_block),
.block_count = static_cast<uint32_t>(last_block - first_block + 1),
};
// Check if write-without-erase goes through
if (auto res = WriteBytesWithoutEraseLocked(page_offset, page_count, std::move(rw_op));
res != ZX_OK) {
zxlogf(INFO, "skipblock: Write without erase failed. %s\n", zx_status_get_string(res));
completer.Reply(res);
return;
}
completer.Reply(ZX_OK);
}
// Called when a block write-without-erase operation finishes. Subsequently queues up
// write-without-erase to the block.
void WriteBytesWithoutEraseCompletionCallback(void* cookie, zx_status_t status,
nand_operation_t* op) {
auto* ctx = static_cast<BlockOperationContext*>(cookie);
if (status != ZX_OK || ctx->current_block == ctx->op.block + ctx->op.block_count ||
(status = ctx->block_map->GetPhysical(ctx->copy, ctx->current_block, &ctx->physical_block)) !=
ZX_OK) {
ctx->status = status;
sync_completion_signal(ctx->completion_event);
return;
}
size_t in_block_offset, write_size, vmo_offset;
ComputeInBlockWriteRangeFromPageRange(ctx, *(ctx->write_page_range), &in_block_offset,
&write_size, &vmo_offset);
const size_t offset_nand =
ctx->physical_block * ctx->nand_info->pages_per_block + in_block_offset;
ZX_ASSERT(write_size <= UINT32_MAX);
ZX_ASSERT(offset_nand <= UINT32_MAX);
op->rw.command = NAND_OP_WRITE;
op->rw.data_vmo = ctx->op.vmo.get();
op->rw.oob_vmo = ZX_HANDLE_INVALID;
op->rw.length = static_cast<uint32_t>(write_size),
op->rw.offset_nand = static_cast<uint32_t>(offset_nand);
op->rw.offset_data_vmo = vmo_offset;
ctx->current_block += 1;
ctx->nand->Queue(op, WriteBytesWithoutEraseCompletionCallback, cookie);
}
zx_status_t SkipBlockDevice::WriteBytesWithoutEraseLocked(size_t page_offset, size_t page_count,
ReadWriteOperation op) {
uint32_t copy = 0;
for (; copy < copy_count_ && op.block < block_map_.AvailableBlockCount(copy); copy++) {
uint32_t physical_block;
if (auto status = block_map_.GetPhysical(copy, op.block, &physical_block); status != ZX_OK) {
return status;
}
sync_completion_t completion;
BlockOperationContext op_context{
.op = std::move(op),
.nand_info = &nand_info_,
.block_map = &block_map_,
.nand = &nand_,
.copy = copy,
.current_block = op.block,
.physical_block = physical_block,
.completion_event = &completion,
.status = ZX_OK,
.mark_bad = false,
.write_page_range = {{page_offset, page_count}},
};
WriteBytesWithoutEraseCompletionCallback(&op_context, ZX_OK, nand_op_->operation());
// Wait on completion.
sync_completion_wait(&completion, ZX_TIME_INFINITE);
op = std::move(op_context.op);
if (op_context.status != ZX_OK) {
zxlogf(ERROR, "Failed to write-without-erase block %d, copy %d with status %s\n",
op_context.current_block, copy, zx_status_get_string(op_context.status));
return op_context.status;
}
}
return copy == copy_count_ ? ZX_OK : ZX_ERR_IO;
}
uint32_t SkipBlockDevice::GetBlockCountLocked() const {
uint32_t logical_block_count = 0;
for (uint32_t copy = 0; copy < copy_count_; copy++) {
logical_block_count = std::max(logical_block_count, block_map_.AvailableBlockCount(copy));
}
return logical_block_count;
}
zx_off_t SkipBlockDevice::DdkGetSize() {
fbl::AutoLock al(&lock_);
return GetBlockSize() * GetBlockCountLocked();
}
zx_status_t SkipBlockDevice::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
llcpp::fuchsia::hardware::skipblock::SkipBlock::Dispatch(this, msg, &transaction);
return transaction.Status();
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = SkipBlockDevice::Create;
return ops;
}();
} // namespace nand
ZIRCON_DRIVER(skip_block, nand::driver_ops, "zircon", "0.1");