blob: 1e90ed96f11c99b28bac3a60970fc817102189b4 [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/nandpart/nandpart.h"
#include <assert.h>
#include <inttypes.h>
#include <lib/operation/nand.h>
#include <lib/sync/completion.h>
#include <stdio.h>
#include <string.h>
#include <zircon/boot/image.h>
#include <zircon/hw/gpt.h>
#include <zircon/types.h>
#include <algorithm>
#include <memory>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/metadata.h>
#include <ddk/metadata/nand.h>
#include <ddk/protocol/badblock.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include "src/devices/nand/drivers/nandpart/nandpart-bind.h"
#include "src/devices/nand/drivers/nandpart/nandpart-utils.h"
namespace nand {
namespace {
constexpr uint8_t fvm_guid[] = GUID_FVM_VALUE;
constexpr uint8_t test_guid[] = GUID_TEST_VALUE;
struct PrivateStorage {
uint32_t offset;
};
using NandPartOp = nand::BorrowedOperation<PrivateStorage>;
// Shim for calling sub-partition's callback.
void CompletionCallback(void* cookie, zx_status_t status, nand_operation_t* nand_op) {
NandPartOp op(nand_op, *static_cast<size_t*>(cookie));
// Re-translate the offsets.
switch (op.operation()->command) {
case NAND_OP_READ:
case NAND_OP_WRITE:
op.operation()->rw.offset_nand -= op.private_storage()->offset;
break;
case NAND_OP_ERASE:
op.operation()->erase.first_block -= op.private_storage()->offset;
break;
default:
ZX_ASSERT(false);
}
op.Complete(status);
}
} // namespace
zx_status_t NandPartDevice::Create(void* ctx, zx_device_t* parent) {
zxlogf(INFO, "NandPartDevice::Create: Starting...!");
nand_protocol_t nand_proto;
if (device_get_protocol(parent, ZX_PROTOCOL_NAND, &nand_proto) != ZX_OK) {
zxlogf(ERROR, "nandpart: parent device '%s': does not support nand protocol",
device_get_name(parent));
return ZX_ERR_NOT_SUPPORTED;
}
// Query parent to get its fuchsia_hardware_nand_Info and size for nand_operation_t.
fuchsia_hardware_nand_Info nand_info;
size_t parent_op_size;
nand_proto.ops->query(nand_proto.ctx, &nand_info, &parent_op_size);
// Make sure parent_op_size is aligned, so we can safely add our data at the end.
parent_op_size = fbl::round_up(parent_op_size, 8u);
// Query parent for nand configuration info.
size_t actual;
nand_config_t nand_config;
zx_status_t status = device_get_metadata(parent, DEVICE_METADATA_PRIVATE, &nand_config,
sizeof(nand_config), &actual);
if (status != ZX_OK) {
zxlogf(ERROR, "nandpart: parent device '%s' has no device metadata", device_get_name(parent));
return status;
}
if (actual < sizeof(nand_config_t)) {
zxlogf(ERROR, "nandpart: Expected metadata is of size %zu, needs to at least be %zu", actual,
sizeof(nand_config_t));
return ZX_ERR_INTERNAL;
}
// Create a bad block instance.
BadBlock::Config config = {
.bad_block_config = nand_config.bad_block_config,
.nand_proto = nand_proto,
};
fbl::RefPtr<BadBlock> bad_block;
status = BadBlock::Create(config, &bad_block);
if (status != ZX_OK) {
zxlogf(ERROR, "nandpart: Failed to create BadBlock object");
return status;
}
// Query parent for partition map.
uint8_t buffer[METADATA_PARTITION_MAP_MAX];
status =
device_get_metadata(parent, DEVICE_METADATA_PARTITION_MAP, buffer, sizeof(buffer), &actual);
if (status != ZX_OK) {
zxlogf(ERROR, "nandpart: parent device '%s' has no partition map", device_get_name(parent));
return status;
}
if (actual < sizeof(zbi_partition_map_t)) {
zxlogf(ERROR, "nandpart: Partition map is of size %zu, needs to at least be %zu", actual,
sizeof(zbi_partition_t));
return ZX_ERR_INTERNAL;
}
auto* pmap = reinterpret_cast<zbi_partition_map_t*>(buffer);
const size_t minimum_size =
sizeof(zbi_partition_map_t) + (sizeof(zbi_partition_t) * pmap->partition_count);
if (actual < minimum_size) {
zxlogf(ERROR, "nandpart: Partition map is of size %zu, needs to at least be %zu", actual,
minimum_size);
return ZX_ERR_INTERNAL;
}
// Sanity check partition map and transform into expected form.
status = SanitizePartitionMap(pmap, nand_info);
if (status != ZX_OK) {
return status;
}
// Create a device for each partition.
for (unsigned i = 0; i < pmap->partition_count; i++) {
const auto* part = &pmap->partitions[i];
nand_info.num_blocks = static_cast<uint32_t>(part->last_block - part->first_block + 1);
memcpy(&nand_info.partition_guid, &part->type_guid, sizeof(nand_info.partition_guid));
// We only use FTL for the FVM partition.
if (memcmp(part->type_guid, fvm_guid, sizeof(fvm_guid)) == 0) {
nand_info.nand_class = fuchsia_hardware_nand_Class_FTL;
} else if (memcmp(part->type_guid, test_guid, sizeof(test_guid)) == 0) {
nand_info.nand_class = fuchsia_hardware_nand_Class_TEST;
} else {
nand_info.nand_class = fuchsia_hardware_nand_Class_BBS;
}
fbl::AllocChecker ac;
std::unique_ptr<NandPartDevice> device(
new (&ac) NandPartDevice(parent, nand_proto, bad_block, parent_op_size, nand_info,
static_cast<uint32_t>(part->first_block)));
if (!ac.check()) {
continue;
}
// Find optional partition_config information.
uint32_t copy_count = 1;
for (uint32_t i = 0; i < nand_config.extra_partition_config_count; i++) {
if (memcmp(nand_config.extra_partition_config[i].type_guid, part->type_guid,
sizeof(part->type_guid)) == 0 &&
nand_config.extra_partition_config[i].copy_count > 0) {
copy_count = nand_config.extra_partition_config[i].copy_count;
break;
}
}
status = device->Bind(part->name, copy_count);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to bind %s with error %d", part->name, status);
continue;
}
// devmgr is now in charge of the device.
__UNUSED auto* dummy = device.release();
}
return ZX_OK;
}
void NandPartDevice::DdkInit(ddk::InitTxn init_txn) {
// Add empty partition map metadata to prevent this driver from binding to its child devices
zx_status_t status = DdkAddMetadata(DEVICE_METADATA_PARTITION_MAP, nullptr, 0);
if (status != ZX_OK) {
init_txn.Reply(status);
return;
}
status = DdkAddMetadata(DEVICE_METADATA_PRIVATE, &extra_partition_copy_count_,
sizeof(extra_partition_copy_count_));
init_txn.Reply(status);
}
zx_status_t NandPartDevice::Bind(const char* name, uint32_t copy_count) {
zxlogf(INFO, "nandpart: Binding %s to %s", name, device_get_name(parent()));
extra_partition_copy_count_ = copy_count;
zx_device_prop_t props[] = {
{BIND_PROTOCOL, 0, ZX_PROTOCOL_NAND},
{BIND_NAND_CLASS, 0, nand_info_.nand_class},
};
return DdkAdd(ddk::DeviceAddArgs(name).set_props(props));
}
void NandPartDevice::NandQuery(fuchsia_hardware_nand_Info* info_out, size_t* nand_op_size_out) {
memcpy(info_out, &nand_info_, sizeof(*info_out));
// Add size of extra context.
*nand_op_size_out = NandPartOp::OperationSize(parent_op_size_);
}
void NandPartDevice::NandQueue(nand_operation_t* nand_op, nand_queue_callback completion_cb,
void* cookie) {
NandPartOp op(nand_op, completion_cb, cookie, parent_op_size_);
uint32_t command = op.operation()->command;
// Make offset relative to full underlying device
switch (command) {
case NAND_OP_READ:
case NAND_OP_WRITE:
op.private_storage()->offset = erase_block_start_ * nand_info_.pages_per_block;
op.operation()->rw.offset_nand += op.private_storage()->offset;
break;
case NAND_OP_ERASE:
op.private_storage()->offset = erase_block_start_;
op.operation()->erase.first_block += erase_block_start_;
break;
default:
op.Complete(ZX_ERR_NOT_SUPPORTED);
return;
}
// Call parent's queue
nand_.Queue(op.take(), CompletionCallback, &parent_op_size_);
}
zx_status_t NandPartDevice::NandGetFactoryBadBlockList(uint32_t* bad_blocks, size_t bad_block_len,
size_t* num_bad_blocks) {
// TODO implement this.
*num_bad_blocks = 0;
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t NandPartDevice::BadBlockGetBadBlockList(uint32_t* bad_block_list,
size_t bad_block_list_len,
size_t* bad_block_count) {
if (!bad_block_list_) {
const zx_status_t status = bad_block_->GetBadBlockList(
erase_block_start_, erase_block_start_ + nand_info_.num_blocks - 1, &bad_block_list_);
if (status != ZX_OK) {
return status;
}
for (uint32_t i = 0; i < bad_block_list_.size(); i++) {
bad_block_list_[i] -= erase_block_start_;
}
}
*bad_block_count = bad_block_list_.size();
zxlogf(DEBUG, "nandpart: %s: Bad block count: %zu", name(), *bad_block_count);
if (bad_block_list_len == 0 || bad_block_list_.size() == 0) {
return ZX_OK;
}
if (bad_block_list == NULL) {
return ZX_ERR_INVALID_ARGS;
}
const size_t size = sizeof(uint32_t) * std::min(*bad_block_count, bad_block_list_len);
memcpy(bad_block_list, bad_block_list_.data(), size);
return ZX_OK;
}
zx_status_t NandPartDevice::BadBlockMarkBlockBad(uint32_t block) {
if (block >= nand_info_.num_blocks) {
return ZX_ERR_OUT_OF_RANGE;
}
// First, invalidate our cached copy.
bad_block_list_.reset();
// Second, "write-through" to actually persist.
block += erase_block_start_;
return bad_block_->MarkBlockBad(block);
}
zx_status_t NandPartDevice::DdkGetProtocol(uint32_t proto_id, void* protocol) {
auto* proto = static_cast<ddk::AnyProtocol*>(protocol);
proto->ctx = this;
switch (proto_id) {
case ZX_PROTOCOL_NAND:
proto->ops = &nand_protocol_ops_;
break;
case ZX_PROTOCOL_BAD_BLOCK:
proto->ops = &bad_block_protocol_ops_;
break;
default:
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = NandPartDevice::Create;
return ops;
}();
} // namespace nand
ZIRCON_DRIVER(nandpart, nand::driver_ops, "zircon", "0.1");