blob: 3eb60791041d3ff36ac48d1b43798de7d0eb2b22 [file] [log] [blame] [edit]
// Copyright 2024 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/android.h"
#include <fidl/fuchsia.system.state/cpp/common_types.h>
#include <lib/fidl/cpp/channel.h>
#include <lib/zx/result.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/system/public/zircon/errors.h>
#include <algorithm>
#include <iterator>
#include <string>
#include <fbl/algorithm.h>
#include "src/storage/lib/paver/boot_control_definition.h"
#include "src/storage/lib/paver/device-partitioner.h"
#include "src/storage/lib/paver/libboot_control.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"
namespace paver {
namespace {
using fuchsia_system_state::SystemPowerState;
} // namespace
zx::result<std::unique_ptr<DevicePartitioner>> AndroidDevicePartitioner::Initialize(
const BlockDevices& devices, fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root,
const PaverConfig& config, fidl::ClientEnd<fuchsia_device::Controller> block_device,
std::shared_ptr<Context> context) {
Arch arch = config.arch;
if (arch != Arch::kX64 && arch != Arch::kArm64) {
return zx::error(ZX_ERR_NOT_FOUND);
}
auto status =
GptDevicePartitioner::InitializeGpt(devices, svc_root, config, std::move(block_device));
if (status.is_error()) {
return status.take_error();
}
if (status->initialize_partition_tables) {
LOG("Found GPT but it was missing expected partitions. The device should be re-initialized "
"via fastboot.\n");
return zx::error(ZX_ERR_BAD_STATE);
}
auto partitioner =
WrapUnique(new AndroidDevicePartitioner(std::move(status->gpt), config, std::move(context)));
LOG("Successfully initialized Android Device Partitioner\n");
return zx::ok(std::move(partitioner));
}
const paver::BlockDevices& AndroidDevicePartitioner::Devices() const { return gpt_->devices(); }
fidl::UnownedClientEnd<fuchsia_io::Directory> AndroidDevicePartitioner::SvcRoot() const {
return gpt_->svc_root();
}
bool AndroidDevicePartitioner::SupportsPartition(const PartitionSpec& spec) const {
constexpr PartitionSpec supported_specs[] = {
PartitionSpec(paver::Partition::kBootloaderA, "boot_shim"),
PartitionSpec(paver::Partition::kBootloaderB, "boot_shim"),
PartitionSpec(paver::Partition::kZirconA),
PartitionSpec(paver::Partition::kZirconB),
PartitionSpec(paver::Partition::kVbMetaA),
PartitionSpec(paver::Partition::kVbMetaB),
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>> AndroidDevicePartitioner::FindPartition(
const PartitionSpec& spec) const {
if (!SupportsPartition(spec)) {
ERROR("Unsupported partition %s\n", spec.ToString().c_str());
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
// Boot shim is passed as kBootloaderX. Meaning we have following mappings:
// boot-shim -> boot
// zircon -> vendor_boot
// vbmeta -> vbmeta
// abrmeta -> misc
// FVM -> super
std::vector<std::string_view> part_names;
switch (spec.partition) {
case Partition::kBootloaderA:
part_names.push_back("boot_a");
break;
case Partition::kBootloaderB:
part_names.push_back("boot_b");
break;
case Partition::kZirconA:
part_names.push_back("vendor_boot_a");
break;
case Partition::kZirconB:
part_names.push_back("vendor_boot_b");
break;
case Partition::kVbMetaA:
part_names.push_back("vbmeta_a");
break;
case Partition::kVbMetaB:
part_names.push_back("vbmeta_b");
break;
case Partition::kAbrMeta:
part_names.push_back("misc");
break;
case Partition::kFuchsiaVolumeManager:
for (const auto& name : config_.system_partition_names) {
part_names.push_back(name);
}
break;
default:
ERROR("Android partitioner cannot find unknown partition type\n");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
const auto filter = [&](const GptPartitionMetadata& part) {
for (const auto& name : part_names) {
if (FilterByName(part, name)) {
return true;
}
}
return false;
};
auto status = gpt_->FindPartition(filter);
if (status.is_error()) {
return status.take_error();
}
return zx::ok(std::move(*status));
}
zx::result<> AndroidDevicePartitioner::WipeFvm() const { return zx::error(ZX_ERR_NOT_SUPPORTED); }
zx::result<> AndroidDevicePartitioner::ResetPartitionTables() const {
ERROR("Initialising partition tables is not supported for Android devices\n");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
zx::result<> AndroidDevicePartitioner::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 (!IsValidAndroidVendorKernel(data)) {
return zx::error(ZX_ERR_BAD_STATE);
}
}
return zx::ok();
}
zx::result<> AndroidDevicePartitioner::OnStop() const {
const auto state = GetShutdownSystemState(gpt_->svc_root());
switch (state) {
case SystemPowerState::kRebootBootloader:
ERROR("Setting one shot reboot to bootloader flag is not implemented.\n");
return zx::error(ZX_ERR_NOT_SUPPORTED);
case SystemPowerState::kRebootRecovery:
ERROR("Setting one shot reboot to recovery flag is not implemented.\n");
return zx::error(ZX_ERR_NOT_SUPPORTED);
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>> AndroidPartitionerFactory::New(
const 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 AndroidDevicePartitioner::Initialize(devices, svc_root, config, std::move(block_device),
std::move(context));
}
class AndroidAbrClient : public abr::Client, android::IoOps {
public:
// IoOps implementation for BootControl
int read(void* data, uint64_t offset, size_t len) const override {
return vmo_.read(data, offset, len);
}
int write(const void* data, uint64_t offset, size_t len) const override {
return vmo_.write(data, offset, len);
}
static zx::result<std::unique_ptr<abr::Client>> Create(
std::unique_ptr<paver::PartitionClient> partition) {
auto status = partition->GetBlockSize();
if (status.is_error()) {
ERROR("Unabled to get block size\n");
return status.take_error();
}
// Make sure block contains BootloaderMessageAB structure.
size_t block_size = std::max(status.value(), sizeof(android::BootloaderMessageAB));
zx::vmo vmo;
if (auto status = zx::make_result(zx::vmo::create(block_size, 0, &vmo)); status.is_error()) {
ERROR("Failed to create vmo\n");
return status.take_error();
}
if (auto status = partition->Read(vmo, block_size); status.is_error()) {
ERROR("Failed to read from partition\n");
return status.take_error();
}
return zx::ok(new AndroidAbrClient(std::move(partition), std::move(vmo), block_size));
}
private:
AndroidAbrClient(std::unique_ptr<paver::PartitionClient> partition, zx::vmo vmo,
size_t block_size)
: Client(/*custom = */ true),
partition_(std::move(partition)),
vmo_(std::move(vmo)),
data_size_(block_size),
boot_control_(*this) {}
zx::result<> Read(uint8_t* buffer, size_t size) override {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
zx::result<> Write(const uint8_t* buffer, size_t size) override {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
android::SlotMetadata ToAndroid(const AbrSlotData& src) {
android::SlotMetadata slot_metadata;
// Max values for src matches bit length for `slot_metadata`
slot_metadata.priority = src.priority;
slot_metadata.tries_remaining = src.tries_remaining;
slot_metadata.successful_boot = src.successful_boot;
slot_metadata.verity_corrupted = 0;
return slot_metadata;
}
AbrSlotData ToFuchsia(const android::SlotMetadata& slot_metadata) {
AbrSlotData abr_slot_data;
// Max values for src matches bit length for `slot_metadata`
abr_slot_data.priority = slot_metadata.priority;
abr_slot_data.tries_remaining = slot_metadata.tries_remaining;
abr_slot_data.successful_boot = slot_metadata.successful_boot;
return abr_slot_data;
}
zx::result<> ReadCustom(AbrSlotData* a, AbrSlotData* b, uint8_t* one_shot_recovery) override {
android::BootloaderControl bootloader_control;
boot_control_.Load(&bootloader_control);
if (bootloader_control.nb_slot != 2) {
ERROR("Only 2-slot systems are supported");
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
*a = ToFuchsia(bootloader_control.slot_info[0]);
*b = ToFuchsia(bootloader_control.slot_info[1]);
// oneshot recovery is not supported via Android ABR
*one_shot_recovery = 0;
return zx::ok();
}
zx::result<> WriteCustom(const AbrSlotData* a, const AbrSlotData* b,
uint8_t one_shot_recovery) override {
android::BootloaderControl bootloader_control;
if (!boot_control_.Load(&bootloader_control)) {
ERROR("Failed to read Bootloader Control data\n");
return zx::error(ZX_ERR_IO);
}
bootloader_control.nb_slot = 2;
bootloader_control.slot_info[0] = ToAndroid(*a);
bootloader_control.slot_info[1] = ToAndroid(*b);
// Oneshot recovery is not supported via Andoird ABR
(void)one_shot_recovery;
if (!boot_control_.UpdateAndSave(&bootloader_control)) {
ERROR("Failed to write Bootloader Control data\n");
return zx::error(ZX_ERR_IO);
}
return zx::ok();
}
zx::result<> Flush() override {
if (auto status = partition_->Write(vmo_, data_size_); status.is_error()) {
ERROR("Failed to read from partition\n");
return status.take_error();
}
return partition_->Flush();
}
std::unique_ptr<paver::PartitionClient> partition_;
zx::vmo vmo_;
size_t data_size_;
android::BootControl boot_control_;
};
zx::result<std::unique_ptr<abr::Client>> AndroidDevicePartitioner::CreateAbrClient() const {
// Assume ABR Metadata partition indicate it is android partition layout.
zx::result partition = FindPartition(paver::PartitionSpec(paver::Partition::kAbrMeta));
if (partition.is_error()) {
ERROR("Failed to find abr partition\n");
return partition.take_error();
}
return AndroidAbrClient::Create(std::move(partition.value()));
}
} // namespace paver