blob: 0dfe13842452095cb75f50e7fc78231720156b76 [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/uefi.h"
#include <lib/fidl/cpp/channel.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/system/public/zircon/errors.h>
#include <algorithm>
#include <cinttypes>
#include <iterator>
#include "fidl/fuchsia.system.state/cpp/common_types.h"
#include "gpt/gpt.h"
#include "src/lib/uuid/uuid.h"
#include "src/storage/lib/paver/device-partitioner.h"
#include "src/storage/lib/paver/gpt.h"
#include "src/storage/lib/paver/pave-logging.h"
#include "src/storage/lib/paver/system_shutdown_state.h"
#include "src/storage/lib/paver/utils.h"
#include "src/storage/lib/paver/validation.h"
#include "zircon/hw/gpt.h"
namespace paver {
namespace {
using fuchsia_system_state::SystemPowerState;
using uuid::Uuid;
constexpr size_t kKibibyte = 1024;
constexpr size_t kMebibyte = kKibibyte * 1024;
constexpr size_t kGibibyte = kMebibyte * 1024;
// TODO: Remove support after July 9th 2021.
constexpr char kOldEfiName[] = "efi-system";
} // namespace
zx::result<std::unique_ptr<DevicePartitioner>> EfiDevicePartitioner::Initialize(
const paver::BlockDevices& devices, fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root,
const PaverConfig& config, fidl::ClientEnd<fuchsia_device::Controller> block_device,
std::shared_ptr<Context> context) {
auto status =
GptDevicePartitioner::InitializeGpt(devices, svc_root, config, std::move(block_device));
if (status.is_error()) {
return status.take_error();
}
if (zx::result find = status->gpt->FindPartition(IsEfiSystemPartition); find.is_error()) {
return find.take_error();
}
auto partitioner =
WrapUnique(new EfiDevicePartitioner(config.arch, std::move(status->gpt), std::move(context)));
if (status->initialize_partition_tables) {
if (auto status = partitioner->ResetPartitionTables(); status.is_error()) {
return status.take_error();
}
}
LOG("Successfully initialized EFI Device Partitioner\n");
return zx::ok(std::move(partitioner));
}
const paver::BlockDevices& EfiDevicePartitioner::Devices() const { return gpt_->devices(); }
fidl::UnownedClientEnd<fuchsia_io::Directory> EfiDevicePartitioner::SvcRoot() const {
return gpt_->svc_root();
}
bool EfiDevicePartitioner::SupportsPartition(const PartitionSpec& spec) const {
const PartitionSpec supported_specs[] = {
PartitionSpec(paver::Partition::kBootloaderA),
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),
PartitionSpec(paver::Partition::kFuchsiaVolumeManager, kOpaqueVolumeContentType),
};
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>> EfiDevicePartitioner::FindPartition(
const PartitionSpec& spec) const {
if (!SupportsPartition(spec)) {
ERROR("Unsupported partition %s\n", spec.ToString().c_str());
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
switch (spec.partition) {
case Partition::kBootloaderA: {
const auto filter = [](const GptPartitionMetadata& part) {
return FilterByTypeAndName(part, GUID_BOOTLOADER_VALUE, GUID_BOOTLOADER_NAME) ||
// TODO(b/400314846) ARM emulator can be run using UEFI. But it uses
// a mix of names and types for bootloader partition
// FilterByTypeAndName(part, GUID_EFI_VALUE, GUID_EFI_NAME) ||
FilterByName(part, GUID_EFI_NAME) ||
// TODO: Remove support after July 9th 2021.
FilterByTypeAndName(part, GUID_EFI_VALUE, kOldEfiName);
};
auto status = gpt_->FindPartition(filter);
if (status.is_error()) {
return status.take_error();
}
return zx::ok(std::move(*status));
}
case Partition::kZirconA:
case Partition::kZirconB:
case Partition::kZirconR:
case Partition::kVbMetaA:
case Partition::kVbMetaB:
case Partition::kVbMetaR:
case Partition::kAbrMeta: {
// New scheme uses name to identify partitions, old uses type GUID.
const char* new_name = PartitionName(spec.partition, PartitionScheme::kNew);
std::optional<Uuid> legacy_type = PartitionTypeGuid(spec.partition, PartitionScheme::kLegacy);
// All supported types for the EfiDevicePartitioner have a known GUID.
ZX_ASSERT(legacy_type);
const auto filter = [&legacy_type, &new_name](const GptPartitionMetadata& part) {
// Try find new scheme partitions first, if fails try falling back to legacy scheme.
// This allows us to support existing devices without requiring re-bootstrapping the GPT.
return FilterByName(part, new_name) || FilterByType(part, *legacy_type);
};
auto status = gpt_->FindPartition(filter);
if (status.is_error()) {
return status.take_error();
}
return zx::ok(std::move(*status));
}
case Partition::kFuchsiaVolumeManager: {
auto status = gpt_->FindPartition(IsFvmPartition);
if (status.is_error()) {
return status.take_error();
}
return zx::ok(std::move(*status));
}
default:
ERROR("EFI partitioner cannot find unknown partition type\n");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
}
zx::result<> EfiDevicePartitioner::WipeFvm() const { return gpt_->WipeFvm(); }
zx::result<> EfiDevicePartitioner::ResetPartitionTables() const {
LOG("Wiping GPT, expect data loss.\n");
using PartitionInitSpec = GptDevicePartitioner::PartitionInitSpec;
const std::array<PartitionInitSpec, 9> fuchsia_partitions{
PartitionInitSpec{
.name = GUID_BOOTLOADER_NAME,
.type = GUID_BOOTLOADER_VALUE,
.size_bytes = 16 * kMebibyte,
},
PartitionInitSpec::ForKnownPartition(Partition::kZirconA, PartitionScheme::kNew,
128 * kMebibyte),
PartitionInitSpec::ForKnownPartition(Partition::kZirconB, PartitionScheme::kNew,
128 * kMebibyte),
PartitionInitSpec::ForKnownPartition(Partition::kZirconR, PartitionScheme::kNew,
192 * kMebibyte),
PartitionInitSpec::ForKnownPartition(Partition::kVbMetaA, PartitionScheme::kNew,
64 * kKibibyte),
PartitionInitSpec::ForKnownPartition(Partition::kVbMetaB, PartitionScheme::kNew,
64 * kKibibyte),
PartitionInitSpec::ForKnownPartition(Partition::kVbMetaR, PartitionScheme::kNew,
64 * kKibibyte),
PartitionInitSpec::ForKnownPartition(Partition::kAbrMeta, PartitionScheme::kNew,
4 * kKibibyte),
PartitionInitSpec::ForKnownPartition(Partition::kFuchsiaVolumeManager, PartitionScheme::kNew,
56 * kGibibyte),
};
const std::array<PartitionInitSpec, 9> fuchsia_partitions_legacy{
PartitionInitSpec{
.name = GUID_EFI_NAME,
.type = GUID_EFI_VALUE,
.size_bytes = 16 * kMebibyte,
},
PartitionInitSpec::ForKnownPartition(Partition::kZirconA, PartitionScheme::kLegacy,
128 * kMebibyte),
PartitionInitSpec::ForKnownPartition(Partition::kZirconB, PartitionScheme::kLegacy,
128 * kMebibyte),
PartitionInitSpec::ForKnownPartition(Partition::kZirconR, PartitionScheme::kLegacy,
192 * kMebibyte),
PartitionInitSpec::ForKnownPartition(Partition::kVbMetaA, PartitionScheme::kLegacy,
64 * kKibibyte),
PartitionInitSpec::ForKnownPartition(Partition::kVbMetaB, PartitionScheme::kLegacy,
64 * kKibibyte),
PartitionInitSpec::ForKnownPartition(Partition::kVbMetaR, PartitionScheme::kLegacy,
64 * kKibibyte),
PartitionInitSpec::ForKnownPartition(Partition::kAbrMeta, PartitionScheme::kLegacy,
4 * kKibibyte),
PartitionInitSpec::ForKnownPartition(Partition::kFuchsiaVolumeManager,
PartitionScheme::kLegacy, 56 * kGibibyte),
};
std::vector<PartitionInitSpec> partitions_to_add;
partitions_to_add.resize(gpt::kPartitionCount);
size_t index = 0;
// To support dual-booting, add back any partitions we've found which are not known to Fuchsia.
zx::result<std::vector<std::unique_ptr<BlockPartitionClient>>> non_fuchsia_partitions =
gpt_->FindAllPartitions([&](const GptPartitionMetadata& part) -> bool {
// There are multiple possible partitions with the ESP type GUID. Filter those which aren't
// from Fuchsia.
if (part.type_guid == Uuid(GUID_EFI_VALUE)) {
return part.name != GUID_EFI_NAME;
}
if (part.type_guid == Uuid(GUID_BOOTLOADER_VALUE)) {
return part.name != GUID_BOOTLOADER_NAME;
}
// For everything else, check if it's a known (Fuchsia-specific) type GUID.
for (const auto& known_partition : fuchsia_partitions) {
if (part.type_guid == known_partition.type) {
return false;
}
}
// Also check for legacy partitions
for (const auto& known_partition : fuchsia_partitions_legacy) {
if (part.type_guid == known_partition.type) {
return false;
}
}
return true;
});
if (non_fuchsia_partitions.is_error()) {
ERROR("Failed to find non-Fuchsia partitions; dual booting may break!: %s\n",
non_fuchsia_partitions.status_string());
} else {
for (auto& partition : non_fuchsia_partitions.value()) {
zx::result metadata = partition->GetMetadata();
zx::result size = partition->GetPartitionSize();
if (metadata.is_ok() && size.is_ok()) {
LOG("Preserving non-Fuchsia partition %s (%s) @ %" PRIu64 "\n", metadata->name.c_str(),
metadata->type_guid.ToString().c_str(), metadata->start_block_offset);
partitions_to_add[index++] = PartitionInitSpec{
.name = std::move(metadata->name),
.type = metadata->type_guid,
.instance = metadata->instance_guid,
.start_block = metadata->start_block_offset,
.size_bytes = *size,
.flags = metadata->flags,
};
} else {
ERROR("Failed to query info for non-Fuchsia partition: %s. Dual booting may break!\n",
metadata.is_error() ? metadata.status_string() : size.status_string());
}
if (index >= partitions_to_add.size()) {
ERROR("Too many partitions found!\n");
return zx::error(ZX_ERR_BAD_STATE);
}
}
}
// Add the known partitions at the end, so the existing partitions get allocated first.
for (const auto& partition : fuchsia_partitions) {
partitions_to_add[index++] = partition;
if (index >= partitions_to_add.size()) {
ERROR("Too many partitions found!\n");
return zx::error(ZX_ERR_BAD_STATE);
}
}
return gpt_->ResetPartitionTables(std::move(partitions_to_add));
}
zx::result<> EfiDevicePartitioner::ValidatePayload(const PartitionSpec& spec,
std::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 (IsZirconPartitionSpec(spec)) {
if (!IsValidKernelZbi(arch_, data)) {
return zx::error(ZX_ERR_BAD_STATE);
}
}
return zx::ok();
}
// Helper function to access `abr_client`.
// Used to set one-shot flags to reboot to recovery/bootloader.
zx::result<> EfiDevicePartitioner::CallAbr(
std::function<zx::result<>(abr::Client&)> call_abr) const {
auto partition = FindPartition(paver::PartitionSpec(paver::Partition::kAbrMeta));
if (partition.is_error()) {
ERROR("Failed to find A/B/R metadata partition: %s\n", partition.status_string());
return partition.take_error();
}
auto abr_partition_client = abr::AbrPartitionClient::Create(std::move(partition.value()));
if (abr_partition_client.is_error()) {
ERROR("Failed to create A/B/R metadata partition client: %s\n",
abr_partition_client.status_string());
return abr_partition_client.take_error();
}
auto& abr_client = abr_partition_client.value();
return call_abr(*abr_client);
}
zx::result<> EfiDevicePartitioner::OnStop() const {
const auto state = GetShutdownSystemState(gpt_->svc_root());
switch (state) {
case SystemPowerState::kRebootBootloader:
LOG("Setting one shot reboot to bootloader flag.\n");
return CallAbr([](abr::Client& abr_client) { return abr_client.SetOneShotBootloader(); });
case SystemPowerState::kRebootRecovery:
LOG("Setting one shot reboot to recovery flag\n");
return CallAbr([](abr::Client& abr_client) { return abr_client.SetOneShotRecovery(); });
case SystemPowerState::kFullyOn:
case SystemPowerState::kReboot:
case SystemPowerState::kPoweroff:
case SystemPowerState::kMexec:
case SystemPowerState::kSuspendRam:
case SystemPowerState::kRebootKernelInitiated:
// nothing to do for these cases
break;
}
return zx::ok();
}
zx::result<std::unique_ptr<DevicePartitioner>> UefiPartitionerFactory::New(
const paver::BlockDevices& devices, fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root,
const PaverConfig& config, std::shared_ptr<Context> context,
fidl::ClientEnd<fuchsia_device::Controller> block_device) {
return EfiDevicePartitioner::Initialize(devices, svc_root, config, std::move(block_device),
std::move(context));
}
zx::result<std::unique_ptr<abr::Client>> EfiDevicePartitioner::CreateAbrClient() const {
// 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 = FindPartition(paver::PartitionSpec(paver::Partition::kAbrMeta));
if (partition.is_error()) {
ERROR("Failed to find abr partition\n");
return partition.take_error();
}
return abr::AbrPartitionClient::Create(std::move(partition.value()));
}
} // namespace paver