blob: da01011e0327ebe339b833ef143e957d0e6ac49e [file] [log] [blame]
// Copyright 2020 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/storage/lib/paver/nelson.h"
#include <lib/fzl/owned-vmo-mapper.h>
#include <lib/stdcompat/span.h>
#include <algorithm>
#include <iterator>
#include <gpt/gpt.h>
#include <soc/aml-common/aml-guid.h>
#include "src/lib/uuid/uuid.h"
#include "src/storage/lib/paver/pave-logging.h"
#include "src/storage/lib/paver/utils.h"
namespace paver {
namespace {
using uuid::Uuid;
} // namespace
zx::result<std::unique_ptr<DevicePartitioner>> NelsonPartitioner::Initialize(
fbl::unique_fd devfs_root, fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root,
fidl::ClientEnd<fuchsia_device::Controller> block_device) {
auto status = IsBoard(devfs_root, "nelson");
if (status.is_error()) {
return status.take_error();
}
auto status_or_gpt =
GptDevicePartitioner::InitializeGpt(std::move(devfs_root), svc_root, std::move(block_device));
if (status_or_gpt.is_error()) {
return status_or_gpt.take_error();
}
auto partitioner = WrapUnique(new NelsonPartitioner(std::move(status_or_gpt->gpt)));
LOG("Successfully initialized NelsonPartitioner Device Partitioner\n");
return zx::ok(std::move(partitioner));
}
bool NelsonPartitioner::SupportsPartition(const PartitionSpec& spec) const {
const PartitionSpec supported_specs[] = {
PartitionSpec(paver::Partition::kBootloaderA, "bl2"),
PartitionSpec(paver::Partition::kBootloaderA, "bootloader"),
PartitionSpec(paver::Partition::kBootloaderB, "bootloader"),
PartitionSpec(paver::Partition::kBootloaderA, "tpl"),
PartitionSpec(paver::Partition::kBootloaderB, "tpl"),
PartitionSpec(paver::Partition::kZirconA),
PartitionSpec(paver::Partition::kZirconB),
PartitionSpec(paver::Partition::kZirconR),
PartitionSpec(paver::Partition::kVbMetaA),
PartitionSpec(paver::Partition::kVbMetaB),
PartitionSpec(paver::Partition::kVbMetaR),
PartitionSpec(paver::Partition::kAbrMeta),
PartitionSpec(paver::Partition::kFuchsiaVolumeManager)};
return std::any_of(std::cbegin(supported_specs), std::cend(supported_specs),
[&](const PartitionSpec& supported) { return SpecMatches(spec, supported); });
}
zx::result<std::unique_ptr<PartitionClient>> NelsonPartitioner::AddPartition(
const PartitionSpec& spec) const {
ERROR("Cannot add partitions to a nelson device\n");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
zx::result<std::unique_ptr<PartitionClient>> NelsonPartitioner::GetEmmcBootPartitionClient() const {
auto boot0_part =
OpenBlockPartition(gpt_->devfs_root(), std::nullopt, Uuid(GUID_EMMC_BOOT1_VALUE), ZX_SEC(5));
if (boot0_part.is_error()) {
return boot0_part.take_error();
}
auto boot0 =
std::make_unique<FixedOffsetBlockPartitionClient>(std::move(boot0_part.value()), 1, 0);
auto boot1_part =
OpenBlockPartition(gpt_->devfs_root(), std::nullopt, Uuid(GUID_EMMC_BOOT2_VALUE), ZX_SEC(5));
if (boot1_part.is_error()) {
return boot1_part.take_error();
}
auto boot1 =
std::make_unique<FixedOffsetBlockPartitionClient>(std::move(boot1_part.value()), 1, 0);
std::vector<std::unique_ptr<PartitionClient>> partitions;
partitions.push_back(std::move(boot0));
partitions.push_back(std::move(boot1));
return zx::ok(std::make_unique<PartitionCopyClient>(std::move(partitions)));
}
zx::result<std::unique_ptr<PartitionClient>> NelsonPartitioner::GetBootloaderPartitionClient(
const PartitionSpec& spec) const {
std::vector<std::unique_ptr<PartitionClient>> partitions;
auto boot_status = GetEmmcBootPartitionClient();
if (boot_status.is_error()) {
ERROR("Failed to find emmc boot partition\n");
return boot_status.take_error();
}
ZX_ASSERT(spec.partition == Partition::kBootloaderA || spec.partition == Partition::kBootloaderB);
PartitionSpec tpl_partition_spec(spec.partition, "tpl");
auto tpl_status = FindPartition(tpl_partition_spec);
if (tpl_status.is_error()) {
ERROR("Failed to find tpl partition\n");
return tpl_status.take_error();
}
auto tpl_block_size_status = tpl_status.value()->GetBlockSize();
if (tpl_block_size_status.is_error()) {
ERROR("Failed to get block size for tpl\n");
return tpl_block_size_status.take_error();
}
size_t block_size = tpl_block_size_status.value();
if (!tpl_status.value()->SupportsBlockPartition()) {
ERROR("tlp partition is not a block device\n");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
BlockPartitionClient* block = reinterpret_cast<BlockPartitionClient*>(tpl_status.value().get());
std::unique_ptr tpl = std::make_unique<FixedOffsetBlockPartitionClient>(
std::move(*block), 0, kNelsonBL2Size / block_size);
return zx::ok(std::make_unique<NelsonBootloaderPartitionClient>(std::move(boot_status.value()),
std::move(tpl)));
}
zx::result<std::unique_ptr<PartitionClient>> NelsonPartitioner::FindPartition(
const PartitionSpec& spec) const {
if (!SupportsPartition(spec)) {
ERROR("Unsupported partition %s\n", spec.ToString().c_str());
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (spec.content_type == "bootloader") {
return GetBootloaderPartitionClient(spec);
}
std::variant<std::string_view, Uuid> part_info;
switch (spec.partition) {
case Partition::kBootloaderA: {
if (spec.content_type == "bl2") {
return GetEmmcBootPartitionClient();
}
if (spec.content_type == "tpl") {
part_info = "tpl_a";
} else {
return zx::error(ZX_ERR_INVALID_ARGS);
}
break;
}
case Partition::kBootloaderB: {
if (spec.content_type == "tpl") {
part_info = "tpl_b";
} else {
return zx::error(ZX_ERR_INVALID_ARGS);
}
break;
}
case Partition::kZirconA:
part_info = Uuid(GUID_ZIRCON_A_VALUE);
break;
case Partition::kZirconB:
part_info = Uuid(GUID_ZIRCON_B_VALUE);
break;
case Partition::kZirconR:
part_info = Uuid(GUID_ZIRCON_R_VALUE);
break;
case Partition::kVbMetaA:
part_info = Uuid(GUID_VBMETA_A_VALUE);
break;
case Partition::kVbMetaB:
part_info = Uuid(GUID_VBMETA_B_VALUE);
break;
case Partition::kVbMetaR:
part_info = Uuid(GUID_VBMETA_R_VALUE);
break;
case Partition::kAbrMeta:
part_info = Uuid(GUID_ABR_META_VALUE);
break;
case Partition::kFuchsiaVolumeManager:
part_info = Uuid(GUID_FVM_VALUE);
break;
default:
ERROR("Partition type is invalid\n");
return zx::error(ZX_ERR_INVALID_ARGS);
}
const auto filter_by_name = [&part_info](const gpt_partition_t& part) {
char cstring_name[GPT_NAME_LEN] = {};
utf16_to_cstring(cstring_name, part.name, GPT_NAME_LEN);
return std::get<std::string_view>(part_info) == std::string_view(cstring_name);
};
if (std::holds_alternative<Uuid>(part_info)) {
auto partition =
OpenBlockPartition(gpt_->devfs_root(), std::nullopt, std::get<Uuid>(part_info), ZX_SEC(5));
if (partition.is_error()) {
return partition.take_error();
}
return zx::ok(new BlockPartitionClient(std::move(partition.value())));
}
auto status = gpt_->FindPartition(std::move(filter_by_name));
if (status.is_error()) {
return status.take_error();
}
return zx::ok(std::move(status->partition));
}
zx::result<> NelsonPartitioner::WipeFvm() const { return gpt_->WipeFvm(); }
zx::result<> NelsonPartitioner::InitPartitionTables() const {
ERROR("Initializing gpt partitions from paver is not supported on nelson\n");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
zx::result<> NelsonPartitioner::WipePartitionTables() const {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
zx::result<> NelsonPartitioner::ValidatePayload(const PartitionSpec& spec,
cpp20::span<const uint8_t> data) const {
if (!SupportsPartition(spec)) {
ERROR("Unsupported partition %s\n", spec.ToString().c_str());
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (spec.content_type == "bootloader") {
if (data.size() <= kNelsonBL2Size) {
ERROR("Payload does not seem to contain tpl image\n");
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
return zx::ok();
}
zx::result<std::unique_ptr<DevicePartitioner>> NelsonPartitionerFactory::New(
fbl::unique_fd devfs_root, fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root, Arch arch,
std::shared_ptr<Context> context, fidl::ClientEnd<fuchsia_device::Controller> block_device) {
return NelsonPartitioner::Initialize(std::move(devfs_root), svc_root, std::move(block_device));
}
zx::result<std::unique_ptr<abr::Client>> NelsonAbrClientFactory::New(
fbl::unique_fd devfs_root, fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root,
std::shared_ptr<paver::Context> context) {
zx::result partitioner = NelsonPartitioner::Initialize(std::move(devfs_root), svc_root, {});
if (partitioner.is_error()) {
return partitioner.take_error();
}
// ABR metadata has no need of a content type since it's always local rather
// than provided in an update package, so just use the default content type.
auto partition = partitioner->FindPartition(paver::PartitionSpec(paver::Partition::kAbrMeta));
if (partition.is_error()) {
return partition.take_error();
}
return abr::AbrPartitionClient::Create(std::move(partition.value()));
}
zx::result<size_t> NelsonBootloaderPartitionClient::GetBlockSize() {
return emmc_boot_client_->GetBlockSize();
}
zx::result<size_t> NelsonBootloaderPartitionClient::GetPartitionSize() {
auto boot_partition_size_status = emmc_boot_client_->GetPartitionSize();
if (boot_partition_size_status.is_error()) {
return boot_partition_size_status.take_error();
}
auto tpl_user_partition_size_status = tpl_client_->GetPartitionSize();
if (tpl_user_partition_size_status.is_error()) {
return tpl_user_partition_size_status.take_error();
}
size_t partition_size = std::min(boot_partition_size_status.value(),
tpl_user_partition_size_status.value() + paver::kNelsonBL2Size);
return zx::ok(partition_size);
}
zx::result<> NelsonBootloaderPartitionClient::Trim() {
if (auto status = emmc_boot_client_->Trim(); status.is_error()) {
return status.take_error();
}
return tpl_client_->Trim();
}
zx::result<> NelsonBootloaderPartitionClient::Flush() {
if (auto status = emmc_boot_client_->Flush(); status.is_error()) {
return status.take_error();
}
return tpl_client_->Flush();
}
zx::result<> NelsonBootloaderPartitionClient::Read(const zx::vmo& vmo, size_t size) {
// Read from boot0/1 first
if (auto status = emmc_boot_client_->Read(vmo, size); status.is_error()) {
return status.take_error();
}
size_t tpl_read_size = size - std::min(kNelsonBL2Size, size);
if (!CheckIfTplSame(vmo, tpl_read_size)) {
LOG("User tpl differs from boot0/1 tpl. Conservatively refusing to read bootloader\n");
return zx::error(ZX_ERR_BAD_STATE);
}
return zx::ok();
}
zx::result<> NelsonBootloaderPartitionClient::Write(const zx::vmo& vmo, size_t vmo_size) {
// write to boot0/1 for the entire combined image.
if (auto status = emmc_boot_client_->Write(vmo, vmo_size); status.is_error()) {
return status.take_error();
}
// write to tpl for only the tpl part.
// tpl_client adds an integral offset equal to the bl2 size when accessing vmo.
// thus the size to write should be adjusted accordingly.
auto buffer_offset_size_status = tpl_client_->GetBufferOffsetInBytes();
if (buffer_offset_size_status.is_error()) {
return buffer_offset_size_status.take_error();
}
size_t write_size = vmo_size - std::min(vmo_size, buffer_offset_size_status.value());
return tpl_client_->Write(vmo, write_size);
}
bool NelsonBootloaderPartitionClient::CheckIfTplSame(const zx::vmo& vmo, size_t tpl_read_size) {
if (!tpl_read_size) {
return true;
}
// Use the size of |vmo| for creating read buffer because it has been adjusted to
// consider block alignment.
uint64_t vmo_size;
if (auto status = vmo.get_size(&vmo_size); status != ZX_OK) {
ERROR("Fail to get vmo_size for read buffer\n");
return false;
}
fzl::OwnedVmoMapper read_tpl;
if (auto status = read_tpl.CreateAndMap(vmo_size, "read-tpl"); status != ZX_OK) {
ERROR("Fail to create vmo for tpl read\n");
return false;
}
if (auto status = tpl_client_->Read(read_tpl.vmo(), tpl_read_size); status.is_error()) {
ERROR("Fail to read tpl\n");
return false;
}
// Compare the tpl part
fzl::VmoMapper mapper;
if (auto status = mapper.Map(vmo, kNelsonBL2Size, 0, ZX_VM_PERM_READ); status != ZX_OK) {
ERROR("Fail to create vmo mapper\n")
return false;
}
const uint8_t* tpl_start = static_cast<const uint8_t*>(read_tpl.start());
return !memcmp(mapper.start(), tpl_start + kNelsonBL2Size, tpl_read_size);
}
} // namespace paver