blob: 01648d681e4b4722e59e8a34f974625463bb7434 [file] [log] [blame]
// Copyright 2019 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 <endian.h>
#include <fcntl.h>
#include <fuchsia/boot/llcpp/fidl.h>
#include <fuchsia/device/llcpp/fidl.h>
#include <fuchsia/fshost/llcpp/fidl.h>
#include <fuchsia/hardware/block/partition/llcpp/fidl.h>
#include <fuchsia/hardware/nand/c/fidl.h>
#include <fuchsia/paver/llcpp/fidl.h>
#include <lib/abr/data.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/cksum.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fidl-async/cpp/bind.h>
#include <lib/fidl/llcpp/string_view.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/paver/provider.h>
#include <lib/service/llcpp/service.h>
#include <lib/sysconfig/sync-client.h>
#include <lib/zx/vmo.h>
#include <zircon/boot/image.h>
#include <zircon/hw/gpt.h>
#include <memory>
#include <optional>
#include <fbl/algorithm.h>
#include <fbl/unique_fd.h>
#include <fs-management/mount.h>
#include <soc/aml-common/aml-guid.h>
#include <zxtest/zxtest.h>
#include "lib/fidl/llcpp/vector_view.h"
#include "src/lib/storage/vfs/cpp/pseudo_dir.h"
#include "src/lib/storage/vfs/cpp/service.h"
#include "src/lib/storage/vfs/cpp/synchronous_vfs.h"
#include "src/storage/lib/paver/device-partitioner.h"
#include "src/storage/lib/paver/luis.h"
#include "src/storage/lib/paver/paver.h"
#include "src/storage/lib/paver/test/test-utils.h"
#include "src/storage/lib/paver/utils.h"
#include "src/storage/lib/utils/topological_path.h"
namespace {
namespace partition = fuchsia_hardware_block_partition;
using devmgr_integration_test::IsolatedDevmgr;
using devmgr_integration_test::RecursiveWaitForFile;
constexpr std::string_view kFirmwareTypeBootloader("");
constexpr std::string_view kFirmwareTypeBl2("bl2");
constexpr std::string_view kFirmwareTypeUnsupported("unsupported_type");
// BL2 images must be exactly this size.
constexpr size_t kBl2ImageSize = 0x10000;
// Make sure we can use our page-based APIs to work with the BL2 image.
static_assert(kBl2ImageSize % kPageSize == 0);
constexpr size_t kBl2ImagePages = kBl2ImageSize / kPageSize;
constexpr uint32_t kBootloaderFirstBlock = 4;
constexpr uint32_t kBl2FirstBlock = 39;
constexpr fuchsia_hardware_nand_RamNandInfo
kNandInfo =
{
.vmo = ZX_HANDLE_INVALID,
.nand_info =
{
.page_size = kPageSize,
.pages_per_block = kPagesPerBlock,
.num_blocks = kNumBlocks,
.ecc_bits = 8,
.oob_size = kOobSize,
.nand_class = fuchsia_hardware_nand_Class_PARTMAP,
.partition_guid = {},
},
.partition_map =
{
.device_guid = {},
.partition_count = 8,
.partitions =
{
{
.type_guid = {},
.unique_guid = {},
.first_block = 0,
.last_block = 3,
.copy_count = 0,
.copy_byte_offset = 0,
.name = {},
.hidden = true,
.bbt = true,
},
{
.type_guid = GUID_BOOTLOADER_VALUE,
.unique_guid = {},
.first_block = kBootloaderFirstBlock,
.last_block = 7,
.copy_count = 0,
.copy_byte_offset = 0,
.name = {'b', 'o', 'o', 't', 'l', 'o', 'a', 'd', 'e', 'r'},
.hidden = false,
.bbt = false,
},
{
.type_guid = GUID_ZIRCON_A_VALUE,
.unique_guid = {},
.first_block = 8,
.last_block = 9,
.copy_count = 0,
.copy_byte_offset = 0,
.name = {'z', 'i', 'r', 'c', 'o', 'n', '-', 'a'},
.hidden = false,
.bbt = false,
},
{
.type_guid = GUID_ZIRCON_B_VALUE,
.unique_guid = {},
.first_block = 10,
.last_block = 11,
.copy_count = 0,
.copy_byte_offset = 0,
.name = {'z', 'i', 'r', 'c', 'o', 'n', '-', 'b'},
.hidden = false,
.bbt = false,
},
{
.type_guid = GUID_ZIRCON_R_VALUE,
.unique_guid = {},
.first_block = 12,
.last_block = 13,
.copy_count = 0,
.copy_byte_offset = 0,
.name = {'z', 'i', 'r', 'c', 'o', 'n', '-', 'r'},
.hidden = false,
.bbt = false,
},
{
.type_guid = GUID_SYS_CONFIG_VALUE,
.unique_guid = {},
.first_block = 14,
.last_block = 17,
.copy_count = 0,
.copy_byte_offset = 0,
.name = {'s', 'y', 's', 'c', 'o', 'n', 'f', 'i', 'g'},
.hidden = false,
.bbt = false,
},
{
.type_guid = GUID_FVM_VALUE,
.unique_guid = {},
.first_block = 18,
.last_block = 38,
.copy_count = 0,
.copy_byte_offset = 0,
.name = {'f', 'v', 'm'},
.hidden = false,
.bbt = false,
},
{
.type_guid = GUID_BL2_VALUE,
.unique_guid = {},
.first_block = kBl2FirstBlock,
.last_block = 39,
.copy_count = 0,
.copy_byte_offset = 0,
.name = {'b', 'l', '2',},
.hidden = false,
.bbt = false,
},
},
},
.export_nand_config = true,
.export_partition_map = true,
};
class FakeBootArgs : public fidl::WireServer<fuchsia_boot::Arguments> {
public:
void GetString(GetStringRequestView request, GetStringCompleter::Sync& completer) override {}
// Stubs
void GetStrings(GetStringsRequestView request, GetStringsCompleter::Sync& completer) override {
std::vector<fidl::StringView> response = {
fidl::StringView::FromExternal(arg_response_),
fidl::StringView(),
};
completer.Reply(fidl::VectorView<fidl::StringView>::FromExternal(response));
}
void GetBool(GetBoolRequestView request, GetBoolCompleter::Sync& completer) override {
if (strncmp(request->key.data(), "astro.sysconfig.abr-wear-leveling",
sizeof("astro.sysconfig.abr-wear-leveling")) == 0) {
completer.Reply(astro_sysconfig_abr_wear_leveling_);
} else {
completer.Reply(request->defaultval);
}
}
void GetBools(GetBoolsRequestView request, GetBoolsCompleter::Sync& completer) override {}
void Collect(CollectRequestView request, CollectCompleter::Sync& completer) override {}
void SetAstroSysConfigAbrWearLeveling(bool opt) { astro_sysconfig_abr_wear_leveling_ = opt; }
void SetArgResponse(std::string arg_response) { arg_response_ = arg_response; }
private:
bool astro_sysconfig_abr_wear_leveling_ = false;
std::string arg_response_ = "-a";
};
class PaverServiceTest : public zxtest::Test {
public:
PaverServiceTest();
~PaverServiceTest();
protected:
void CreatePayload(size_t num_pages, fuchsia_mem::wire::Buffer* out);
static constexpr size_t kKilobyte = 1 << 10;
void ValidateWritten(const fuchsia_mem::wire::Buffer& buf, size_t num_pages) {
ASSERT_GE(buf.size, num_pages * kPageSize);
fzl::VmoMapper mapper;
ASSERT_OK(mapper.Map(buf.vmo, 0,
fbl::round_up(num_pages * kPageSize, zx_system_get_page_size()),
ZX_VM_PERM_READ));
const uint8_t* start = reinterpret_cast<uint8_t*>(mapper.start());
for (size_t i = 0; i < num_pages * kPageSize; i++) {
ASSERT_EQ(start[i], 0x4a, "i = %zu", i);
}
}
void* provider_ctx_ = nullptr;
std::optional<fidl::WireSyncClient<fuchsia_paver::Paver>> client_;
async::Loop loop_;
// The paver makes synchronous calls into /svc, so it must run in a separate loop to not
// deadlock.
async::Loop loop2_;
FakeSvc<FakeBootArgs> fake_svc_;
};
PaverServiceTest::PaverServiceTest()
: loop_(&kAsyncLoopConfigAttachToCurrentThread),
loop2_(&kAsyncLoopConfigNoAttachToCurrentThread),
fake_svc_(loop2_.dispatcher(), FakeBootArgs()) {
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
client_.emplace(std::move(client));
ASSERT_OK(paver_get_service_provider()->ops->init(&provider_ctx_));
ASSERT_OK(paver_get_service_provider()->ops->connect(
provider_ctx_, loop_.dispatcher(), fidl::DiscoverableProtocolName<fuchsia_paver::Paver>,
server.release()));
loop_.StartThread("paver-svc-test-loop");
loop2_.StartThread("paver-svc-test-loop-2");
}
PaverServiceTest::~PaverServiceTest() {
loop_.Shutdown();
loop2_.Shutdown();
paver_get_service_provider()->ops->release(provider_ctx_);
provider_ctx_ = nullptr;
}
void PaverServiceTest::CreatePayload(size_t num_pages, fuchsia_mem::wire::Buffer* out) {
zx::vmo vmo;
fzl::VmoMapper mapper;
const size_t size = kPageSize * num_pages;
ASSERT_OK(mapper.CreateAndMap(size, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr, &vmo));
memset(mapper.start(), 0x4a, mapper.size());
out->vmo = std::move(vmo);
out->size = size;
}
class PaverServiceSkipBlockTest : public PaverServiceTest {
public:
// Initializes the RAM NAND device.
void InitializeRamNand(const fuchsia_hardware_nand_RamNandInfo& nand_info = kNandInfo) {
ASSERT_NO_FATAL_FAILURES(SpawnIsolatedDevmgr(nand_info));
ASSERT_NO_FATAL_FAILURES(WaitForDevices());
}
protected:
void SpawnIsolatedDevmgr(const fuchsia_hardware_nand_RamNandInfo& nand_info) {
ASSERT_EQ(device_.get(), nullptr);
ASSERT_NO_FATAL_FAILURES(SkipBlockDevice::Create(nand_info, &device_));
static_cast<paver::Paver*>(provider_ctx_)->set_dispatcher(loop_.dispatcher());
static_cast<paver::Paver*>(provider_ctx_)->set_devfs_root(device_->devfs_root());
static_cast<paver::Paver*>(provider_ctx_)->set_svc_root(std::move(fake_svc_.svc_chan()));
}
void WaitForDevices() {
fbl::unique_fd fd;
ASSERT_OK(RecursiveWaitForFile(device_->devfs_root(),
"misc/nand-ctl/ram-nand-0/sysconfig/skip-block", &fd));
ASSERT_OK(RecursiveWaitForFile(device_->devfs_root(), "misc/nand-ctl/ram-nand-0/fvm/ftl/block",
&fvm_));
}
void FindBootManager() {
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
auto result = client_->FindBootManager(std::move(remote));
ASSERT_OK(result.status());
boot_manager_.emplace(std::move(local));
}
void FindDataSink() {
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
auto result = client_->FindDataSink(std::move(remote));
ASSERT_OK(result.status());
data_sink_.emplace(std::move(local));
}
void FindSysconfig() {
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
auto result = client_->FindSysconfig(std::move(remote));
ASSERT_OK(result.status());
sysconfig_.emplace(std::move(local));
}
void SetAbr(const AbrData& data) {
auto* buf = reinterpret_cast<uint8_t*>(device_->mapper().start()) + (14 * kSkipBlockSize) +
(60 * kKilobyte);
*reinterpret_cast<AbrData*>(buf) = data;
}
AbrData GetAbr() {
auto* buf = reinterpret_cast<uint8_t*>(device_->mapper().start()) + (14 * kSkipBlockSize) +
(60 * kKilobyte);
return *reinterpret_cast<AbrData*>(buf);
}
// Equivalence of GetAbr() in the context of abr wear-leveling.
// Since there can be multiple pages in abr sub-partition that may have valid abr data,
// argument |copy_index| is used to read a specific one.
AbrData GetAbrInWearLeveling(const sysconfig_header& header, size_t copy_index) {
auto* buf = reinterpret_cast<uint8_t*>(device_->mapper().start()) + (14 * kSkipBlockSize) +
header.abr_metadata.offset + copy_index * 4 * kKilobyte;
AbrData ret;
memcpy(&ret, buf, sizeof(ret));
return ret;
}
using PaverServiceTest::ValidateWritten;
// Checks that the device mapper contains |expected| at each byte in the given
// range. Uses ASSERT_EQ() per-byte to give a helpful message on failure.
void AssertContents(size_t offset, size_t length, uint8_t expected) {
const uint8_t* contents = static_cast<uint8_t*>(device_->mapper().start()) + offset;
for (size_t i = 0; i < length; i++) {
ASSERT_EQ(expected, contents[i], "i = %zu", i);
}
}
void ValidateWritten(uint32_t block, size_t num_blocks) {
AssertContents(block * kSkipBlockSize, num_blocks * kSkipBlockSize, 0x4A);
}
void ValidateUnwritten(uint32_t block, size_t num_blocks) {
AssertContents(block * kSkipBlockSize, num_blocks * kSkipBlockSize, 0xFF);
}
void ValidateWrittenPages(uint32_t page, size_t num_pages) {
AssertContents(page * kPageSize, num_pages * kPageSize, 0x4A);
}
void ValidateUnwrittenPages(uint32_t page, size_t num_pages) {
AssertContents(page * kPageSize, num_pages * kPageSize, 0xFF);
}
void ValidateWrittenBytes(size_t offset, size_t num_bytes) {
AssertContents(offset, num_bytes, 0x4A);
}
void ValidateUnwrittenBytes(size_t offset, size_t num_bytes) {
AssertContents(offset, num_bytes, 0xFF);
}
void WriteData(uint32_t page, size_t num_pages, uint8_t data) {
WriteDataBytes(page * kPageSize, num_pages * kPageSize, data);
}
void WriteDataBytes(uint32_t start, size_t num_bytes, uint8_t data) {
memset(static_cast<uint8_t*>(device_->mapper().start()) + start, data, num_bytes);
}
void WriteDataBytes(uint32_t start, void* data, size_t num_bytes) {
memcpy(static_cast<uint8_t*>(device_->mapper().start()) + start, data, num_bytes);
}
void TestSysconfigWriteBufferedClient(uint32_t offset_in_pages, uint32_t sysconfig_pages);
void TestSysconfigWipeBufferedClient(uint32_t offset_in_pages, uint32_t sysconfig_pages);
std::optional<fidl::WireSyncClient<fuchsia_paver::BootManager>> boot_manager_;
std::optional<fidl::WireSyncClient<fuchsia_paver::DataSink>> data_sink_;
std::optional<fidl::WireSyncClient<fuchsia_paver::Sysconfig>> sysconfig_;
std::unique_ptr<SkipBlockDevice> device_;
fbl::unique_fd fvm_;
};
constexpr AbrData kAbrData = {
.magic = {'\0', 'A', 'B', '0'},
.version_major = kAbrMajorVersion,
.version_minor = kAbrMinorVersion,
.reserved1 = {},
.slot_data =
{
{
.priority = 0,
.tries_remaining = 0,
.successful_boot = 0,
.reserved = {},
},
{
.priority = 1,
.tries_remaining = 0,
.successful_boot = 1,
.reserved = {},
},
},
.one_shot_recovery_boot = 0,
.reserved2 = {},
.crc32 = {},
};
void ComputeCrc(AbrData* data) {
data->crc32 = htobe32(crc32(0, reinterpret_cast<const uint8_t*>(data), offsetof(AbrData, crc32)));
}
TEST_F(PaverServiceSkipBlockTest, InitializeAbr) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = {};
memset(&abr_data, 0x3d, sizeof(abr_data));
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryActiveConfiguration();
ASSERT_OK(result.status());
}
TEST_F(PaverServiceSkipBlockTest, InitializeAbrAlreadyValid) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryActiveConfiguration();
ASSERT_OK(result.status());
}
TEST_F(PaverServiceSkipBlockTest, QueryActiveConfigurationInvalidAbr) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = {};
memset(&abr_data, 0x3d, sizeof(abr_data));
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryActiveConfiguration();
ASSERT_OK(result.status());
}
TEST_F(PaverServiceSkipBlockTest, QueryActiveConfigurationBothPriority0) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
abr_data.slot_data[0].priority = 0;
abr_data.slot_data[1].priority = 0;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryActiveConfiguration();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_err());
ASSERT_STATUS(result->result.err(), ZX_ERR_NOT_SUPPORTED);
}
TEST_F(PaverServiceSkipBlockTest, QueryActiveConfigurationSlotB) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryActiveConfiguration();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().configuration, fuchsia_paver::wire::Configuration::kB);
}
TEST_F(PaverServiceSkipBlockTest, QueryActiveConfigurationSlotA) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
abr_data.slot_data[0].priority = 2;
abr_data.slot_data[0].successful_boot = 1;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryActiveConfiguration();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().configuration, fuchsia_paver::wire::Configuration::kA);
}
TEST_F(PaverServiceSkipBlockTest, QueryCurrentConfigurationSlotA) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryCurrentConfiguration();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().configuration, fuchsia_paver::wire::Configuration::kA);
}
TEST_F(PaverServiceSkipBlockTest, QueryCurrentConfigurationSlotB) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fake_svc_.fake_boot_args().SetArgResponse("-b");
AbrData abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryCurrentConfiguration();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().configuration, fuchsia_paver::wire::Configuration::kB);
}
TEST_F(PaverServiceSkipBlockTest, QueryCurrentConfigurationSlotR) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fake_svc_.fake_boot_args().SetArgResponse("-r");
AbrData abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryCurrentConfiguration();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().configuration, fuchsia_paver::wire::Configuration::kRecovery);
}
TEST_F(PaverServiceSkipBlockTest, QueryCurrentConfigurationSlotInvalid) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fake_svc_.fake_boot_args().SetArgResponse("");
AbrData abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryCurrentConfiguration();
ASSERT_STATUS(result, ZX_ERR_PEER_CLOSED);
}
TEST_F(PaverServiceSkipBlockTest, QueryConfigurationStatusHealthy) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
auto abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryConfigurationStatus(fuchsia_paver::wire::Configuration::kB);
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().status, fuchsia_paver::wire::ConfigurationStatus::kHealthy);
}
TEST_F(PaverServiceSkipBlockTest, QueryConfigurationStatusPending) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
abr_data.slot_data[1].successful_boot = 0;
abr_data.slot_data[1].tries_remaining = 1;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryConfigurationStatus(fuchsia_paver::wire::Configuration::kB);
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().status, fuchsia_paver::wire::ConfigurationStatus::kPending);
}
TEST_F(PaverServiceSkipBlockTest, QueryConfigurationStatusUnbootable) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->QueryConfigurationStatus(fuchsia_paver::wire::Configuration::kA);
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().status,
fuchsia_paver::wire::ConfigurationStatus::kUnbootable);
}
TEST_F(PaverServiceSkipBlockTest, SetConfigurationActive) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
abr_data.slot_data[0].priority = kAbrMaxPriority;
abr_data.slot_data[0].tries_remaining = kAbrMaxTriesRemaining;
abr_data.slot_data[0].successful_boot = 0;
ComputeCrc(&abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
{
auto result = boot_manager_->SetConfigurationActive(fuchsia_paver::wire::Configuration::kA);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
auto result = boot_manager_->Flush();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
auto actual = GetAbr();
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
}
TEST_F(PaverServiceSkipBlockTest, SetConfigurationActiveRollover) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
abr_data.slot_data[1].priority = kAbrMaxPriority;
ComputeCrc(&abr_data);
SetAbr(abr_data);
abr_data.slot_data[1].priority = kAbrMaxPriority - 1;
abr_data.slot_data[0].priority = kAbrMaxPriority;
abr_data.slot_data[0].tries_remaining = kAbrMaxTriesRemaining;
abr_data.slot_data[0].successful_boot = 0;
ComputeCrc(&abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
{
auto result = boot_manager_->SetConfigurationActive(fuchsia_paver::wire::Configuration::kA);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
auto result = boot_manager_->Flush();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
auto actual = GetAbr();
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
}
TEST_F(PaverServiceSkipBlockTest, SetConfigurationUnbootableSlotA) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
abr_data.slot_data[0].priority = 2;
abr_data.slot_data[0].tries_remaining = 3;
abr_data.slot_data[0].successful_boot = 0;
ComputeCrc(&abr_data);
SetAbr(abr_data);
abr_data.slot_data[0].priority = 0;
abr_data.slot_data[0].tries_remaining = 0;
abr_data.slot_data[0].successful_boot = 0;
ComputeCrc(&abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
{
auto result = boot_manager_->SetConfigurationUnbootable(fuchsia_paver::wire::Configuration::kA);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
auto result = boot_manager_->Flush();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
auto actual = GetAbr();
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
}
TEST_F(PaverServiceSkipBlockTest, SetConfigurationUnbootableSlotB) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
abr_data.slot_data[1].tries_remaining = 3;
abr_data.slot_data[1].successful_boot = 0;
ComputeCrc(&abr_data);
SetAbr(abr_data);
abr_data.slot_data[1].priority = 0;
abr_data.slot_data[1].tries_remaining = 0;
abr_data.slot_data[1].successful_boot = 0;
ComputeCrc(&abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
{
auto result = boot_manager_->SetConfigurationUnbootable(fuchsia_paver::wire::Configuration::kB);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
auto result = boot_manager_->Flush();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
auto actual = GetAbr();
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
}
TEST_F(PaverServiceSkipBlockTest, SetConfigurationHealthySlotA) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
abr_data.slot_data[0].priority = kAbrMaxPriority;
abr_data.slot_data[0].tries_remaining = 0;
abr_data.slot_data[0].successful_boot = 1;
abr_data.slot_data[1].priority = 0;
abr_data.slot_data[1].tries_remaining = 0;
abr_data.slot_data[1].successful_boot = 0;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
{
auto result = boot_manager_->SetConfigurationHealthy(fuchsia_paver::wire::Configuration::kA);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
auto result = boot_manager_->Flush();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
auto actual = GetAbr();
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
}
TEST_F(PaverServiceSkipBlockTest, SetConfigurationHealthySlotB) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ComputeCrc(&abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
{
auto result = boot_manager_->SetConfigurationHealthy(fuchsia_paver::wire::Configuration::kB);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
auto result = boot_manager_->Flush();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
auto actual = GetAbr();
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
}
TEST_F(PaverServiceSkipBlockTest, SetConfigurationHealthySlotR) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result =
boot_manager_->SetConfigurationHealthy(fuchsia_paver::wire::Configuration::kRecovery);
ASSERT_OK(result.status());
ASSERT_EQ(result->status, ZX_ERR_INVALID_ARGS);
}
TEST_F(PaverServiceSkipBlockTest, SetConfigurationHealthyBothUnknown) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
abr_data.slot_data[0].priority = kAbrMaxPriority;
abr_data.slot_data[0].tries_remaining = 3;
abr_data.slot_data[0].successful_boot = 0;
abr_data.slot_data[1].priority = kAbrMaxPriority - 1;
abr_data.slot_data[1].tries_remaining = 3;
abr_data.slot_data[1].successful_boot = 0;
ComputeCrc(&abr_data);
SetAbr(abr_data);
abr_data.slot_data[0].tries_remaining = 0;
abr_data.slot_data[0].successful_boot = 1;
abr_data.slot_data[1].tries_remaining = kAbrMaxTriesRemaining;
ComputeCrc(&abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
{
auto result = boot_manager_->SetConfigurationHealthy(fuchsia_paver::wire::Configuration::kA);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
auto result = boot_manager_->Flush();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
auto actual = GetAbr();
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
}
TEST_F(PaverServiceSkipBlockTest, SetConfigurationHealthyOtherHealthy) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
abr_data.slot_data[0].priority = kAbrMaxPriority - 1;
abr_data.slot_data[0].tries_remaining = 0;
abr_data.slot_data[0].successful_boot = 1;
abr_data.slot_data[1].priority = kAbrMaxPriority;
abr_data.slot_data[1].tries_remaining = 3;
abr_data.slot_data[1].successful_boot = 0;
ComputeCrc(&abr_data);
SetAbr(abr_data);
abr_data.slot_data[0].tries_remaining = kAbrMaxTriesRemaining;
abr_data.slot_data[0].successful_boot = 0;
abr_data.slot_data[1].tries_remaining = 0;
abr_data.slot_data[1].successful_boot = 1;
ComputeCrc(&abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
{
auto result = boot_manager_->SetConfigurationHealthy(fuchsia_paver::wire::Configuration::kB);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
auto result = boot_manager_->Flush();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
auto actual = GetAbr();
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
}
TEST_F(PaverServiceSkipBlockTest, SetUnbootableConfigurationHealthy) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
auto result = boot_manager_->SetConfigurationHealthy(fuchsia_paver::wire::Configuration::kA);
ASSERT_OK(result.status());
ASSERT_EQ(result->status, ZX_ERR_INVALID_ARGS);
}
TEST_F(PaverServiceSkipBlockTest, BootManagerBuffered) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
AbrData abr_data = kAbrData;
// Successful slot b, active slot a. Like what happen after a reboot following an OTA.
abr_data.slot_data[0].tries_remaining = 3;
abr_data.slot_data[0].successful_boot = 0;
abr_data.slot_data[0].priority = 1;
ComputeCrc(&abr_data);
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindBootManager());
{
auto result = boot_manager_->QueryActiveConfiguration();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().configuration, fuchsia_paver::wire::Configuration::kA);
}
{
auto result = boot_manager_->SetConfigurationHealthy(fuchsia_paver::wire::Configuration::kA);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
{
auto result = boot_manager_->SetConfigurationUnbootable(fuchsia_paver::wire::Configuration::kB);
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
// haven't flushed yet, storage shall stay the same.
auto abr = GetAbr();
ASSERT_BYTES_EQ(&abr, &abr_data, sizeof(abr));
{
auto result = boot_manager_->Flush();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
abr_data.slot_data[0].tries_remaining = 0;
abr_data.slot_data[0].successful_boot = 1;
abr_data.slot_data[0].priority = 1;
abr_data.slot_data[1].tries_remaining = 0;
abr_data.slot_data[1].successful_boot = 0;
abr_data.slot_data[1].priority = 0;
ComputeCrc(&abr_data);
abr = GetAbr();
ASSERT_BYTES_EQ(&abr, &abr_data, sizeof(abr));
}
TEST_F(PaverServiceSkipBlockTest, WriteAssetKernelConfigA) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fuchsia_mem::wire::Buffer payload;
CreatePayload(2 * kPagesPerBlock, &payload);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->WriteAsset(fuchsia_paver::wire::Configuration::kA,
fuchsia_paver::wire::Asset::kKernel, std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result->status);
ValidateWritten(8, 2);
ValidateUnwritten(10, 4);
}
TEST_F(PaverServiceSkipBlockTest, WriteAssetKernelConfigB) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fuchsia_mem::wire::Buffer payload;
CreatePayload(2 * kPagesPerBlock, &payload);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->WriteAsset(fuchsia_paver::wire::Configuration::kB,
fuchsia_paver::wire::Asset::kKernel, std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
ValidateUnwritten(8, 2);
ValidateWritten(10, 2);
ValidateUnwritten(12, 2);
}
TEST_F(PaverServiceSkipBlockTest, WriteAssetKernelConfigRecovery) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fuchsia_mem::wire::Buffer payload;
CreatePayload(2 * kPagesPerBlock, &payload);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->WriteAsset(fuchsia_paver::wire::Configuration::kRecovery,
fuchsia_paver::wire::Asset::kKernel, std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
ValidateUnwritten(8, 4);
ValidateWritten(12, 2);
}
TEST_F(PaverServiceSkipBlockTest, WriteAssetVbMetaConfigA) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fuchsia_mem::wire::Buffer payload;
CreatePayload(32, &payload);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result =
data_sink_->WriteAsset(fuchsia_paver::wire::Configuration::kA,
fuchsia_paver::wire::Asset::kVerifiedBootMetadata, std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
auto sync_result = data_sink_->Flush();
ASSERT_OK(sync_result.status());
ASSERT_OK(sync_result.value().status);
ValidateWrittenPages(14 * kPagesPerBlock + 32, 32);
}
TEST_F(PaverServiceSkipBlockTest, WriteAssetVbMetaConfigB) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fuchsia_mem::wire::Buffer payload;
CreatePayload(32, &payload);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result =
data_sink_->WriteAsset(fuchsia_paver::wire::Configuration::kB,
fuchsia_paver::wire::Asset::kVerifiedBootMetadata, std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
auto sync_result = data_sink_->Flush();
ASSERT_OK(sync_result.status());
ASSERT_OK(sync_result.value().status);
ValidateWrittenPages(14 * kPagesPerBlock + 64, 32);
}
TEST_F(PaverServiceSkipBlockTest, WriteAssetVbMetaConfigRecovery) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fuchsia_mem::wire::Buffer payload;
CreatePayload(32, &payload);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result =
data_sink_->WriteAsset(fuchsia_paver::wire::Configuration::kRecovery,
fuchsia_paver::wire::Asset::kVerifiedBootMetadata, std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
auto sync_result = data_sink_->Flush();
ASSERT_OK(sync_result.status());
ASSERT_OK(sync_result.value().status);
ValidateWrittenPages(14 * kPagesPerBlock + 96, 32);
}
TEST_F(PaverServiceSkipBlockTest, AbrWearLevelingLayoutNotUpdated) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
// Enable write-caching + abr metadata wear-leveling
fake_svc_.fake_boot_args().SetAstroSysConfigAbrWearLeveling(true);
// Active slot b
AbrData abr_data = kAbrData;
abr_data.slot_data[0].tries_remaining = 3;
abr_data.slot_data[0].successful_boot = 0;
abr_data.slot_data[0].priority = 0;
abr_data.slot_data[1].tries_remaining = 3;
abr_data.slot_data[1].successful_boot = 0;
abr_data.slot_data[1].priority = 1;
ComputeCrc(&abr_data);
SetAbr(abr_data);
// Layout will not be updated as A/B state does not meet requirement.
// (one successful slot + one unbootable slot)
ASSERT_NO_FATAL_FAILURES(FindBootManager());
{
auto result = boot_manager_->QueryActiveConfiguration();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().configuration, fuchsia_paver::wire::Configuration::kB);
}
{
auto result = boot_manager_->SetConfigurationHealthy(fuchsia_paver::wire::Configuration::kB);
ASSERT_OK(result.status());
}
{
// The query result will come from the cache as flushed is not called.
// Validate that it is correct.
auto result = boot_manager_->QueryActiveConfiguration();
ASSERT_OK(result.status());
ASSERT_EQ(result->result.response().configuration, fuchsia_paver::wire::Configuration::kB);
}
{
// Mark the old slot A as unbootable.
auto set_unbootable_result =
boot_manager_->SetConfigurationUnbootable(fuchsia_paver::wire::Configuration::kA);
ASSERT_OK(set_unbootable_result.status());
}
// Haven't flushed yet. abr data in storage should stayed the same.
auto actual = GetAbr();
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
{
auto result_sync = boot_manager_->Flush();
ASSERT_OK(result_sync.status());
ASSERT_OK(result_sync->status);
}
// Expected result: unbootable slot a, successful active slot b
abr_data.slot_data[0].tries_remaining = 0;
abr_data.slot_data[0].successful_boot = 0;
abr_data.slot_data[0].priority = 0;
abr_data.slot_data[1].tries_remaining = 0;
abr_data.slot_data[1].successful_boot = 1;
abr_data.slot_data[1].priority = 1;
ComputeCrc(&abr_data);
// Validate that new abr data is flushed to memory.
// Since layout is not updated, Abr metadata is expected to be at the traditional position
// (16th page).
actual = GetAbr();
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
}
AbrData GetAbrWearlevelingSupportingLayout() {
// Unbootable slot a, successful active slot b
AbrData abr_data = kAbrData;
abr_data.slot_data[0].tries_remaining = 0;
abr_data.slot_data[0].successful_boot = 0;
abr_data.slot_data[0].priority = 0;
abr_data.slot_data[1].tries_remaining = 0;
abr_data.slot_data[1].successful_boot = 1;
abr_data.slot_data[1].priority = 1;
ComputeCrc(&abr_data);
return abr_data;
}
TEST_F(PaverServiceSkipBlockTest, AbrWearLevelingLayoutUpdated) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
// Enable write-caching + abr metadata wear-leveling
fake_svc_.fake_boot_args().SetAstroSysConfigAbrWearLeveling(true);
// Unbootable slot a, successful active slot b
auto abr_data = GetAbrWearlevelingSupportingLayout();
SetAbr(abr_data);
// Layout will be updated. Since A/B state is one successful + one unbootable
ASSERT_NO_FATAL_FAILURES(FindBootManager());
{
auto result = boot_manager_->QueryActiveConfiguration();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().configuration, fuchsia_paver::wire::Configuration::kB);
}
{
auto result = boot_manager_->SetConfigurationActive(fuchsia_paver::wire::Configuration::kA);
ASSERT_OK(result.status());
}
{
// The query result will come from the cache as we haven't flushed.
// Validate that it is correct.
auto result = boot_manager_->QueryActiveConfiguration();
ASSERT_OK(result.status());
ASSERT_EQ(result->result.response().configuration, fuchsia_paver::wire::Configuration::kA);
}
// Haven't flushed yet. abr data in storage should stayed the same.
// Since layout changed, use the updated layout to find abr.
auto header = sysconfig::SyncClientAbrWearLeveling::GetAbrWearLevelingSupportedLayout();
auto actual = GetAbrInWearLeveling(header, 0);
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
{
auto result_sync = boot_manager_->Flush();
ASSERT_OK(result_sync.status());
ASSERT_OK(result_sync->status);
}
// Expected result: successful slot a, active slot b with max tries and priority.
abr_data.slot_data[0].tries_remaining = kAbrMaxTriesRemaining;
abr_data.slot_data[0].successful_boot = 0;
abr_data.slot_data[0].priority = kAbrMaxPriority;
abr_data.slot_data[1].tries_remaining = 0;
abr_data.slot_data[1].successful_boot = 1;
abr_data.slot_data[1].priority = 1;
ComputeCrc(&abr_data);
// Validate that new abr data is flushed to memory.
// The first page (page 0) in the abr sub-partition is occupied by the initial abr data.
// Thus, the new abr metadata is expected to be appended at the 2nd page (page 1).
actual = GetAbrInWearLeveling(header, 1);
ASSERT_BYTES_EQ(&abr_data, &actual, sizeof(abr_data));
}
TEST_F(PaverServiceSkipBlockTest, WriteAssetBuffered) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
ASSERT_NO_FATAL_FAILURES(FindDataSink());
fuchsia_paver::wire::Configuration configs[] = {fuchsia_paver::wire::Configuration::kA,
fuchsia_paver::wire::Configuration::kB,
fuchsia_paver::wire::Configuration::kRecovery};
for (auto config : configs) {
fuchsia_mem::wire::Buffer payload;
CreatePayload(32, &payload);
auto result = data_sink_->WriteAsset(config, fuchsia_paver::wire::Asset::kVerifiedBootMetadata,
std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
}
ValidateUnwrittenPages(14 * kPagesPerBlock + 32, 96);
auto sync_result = data_sink_->Flush();
ASSERT_OK(sync_result.status());
ASSERT_OK(sync_result.value().status);
ValidateWrittenPages(14 * kPagesPerBlock + 32, 96);
}
TEST_F(PaverServiceSkipBlockTest, WriteAssetTwice) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fuchsia_mem::wire::Buffer payload;
CreatePayload(2 * kPagesPerBlock, &payload);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
{
auto result = data_sink_->WriteAsset(fuchsia_paver::wire::Configuration::kA,
fuchsia_paver::wire::Asset::kKernel, std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
CreatePayload(2 * kPagesPerBlock, &payload);
ValidateWritten(8, 2);
ValidateUnwritten(10, 4);
}
{
auto result = data_sink_->WriteAsset(fuchsia_paver::wire::Configuration::kA,
fuchsia_paver::wire::Asset::kKernel, std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
ValidateWritten(8, 2);
ValidateUnwritten(10, 4);
}
}
TEST_F(PaverServiceSkipBlockTest, WriteFirmwareConfigASupported) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
ASSERT_NO_FATAL_FAILURES(FindDataSink());
fuchsia_mem::wire::Buffer payload;
CreatePayload(4 * kPagesPerBlock, &payload);
auto result = data_sink_->WriteFirmware(fuchsia_paver::wire::Configuration::kA,
fidl::StringView::FromExternal(kFirmwareTypeBootloader),
std::move(payload));
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_status());
ASSERT_OK(result->result.status());
ValidateWritten(kBootloaderFirstBlock, 4);
WriteData(kBootloaderFirstBlock, 4 * kPagesPerBlock, 0xff);
}
TEST_F(PaverServiceSkipBlockTest, WriteFirmwareUnsupportedConfigBFallBackToA) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
ASSERT_NO_FATAL_FAILURES(FindDataSink());
fuchsia_mem::wire::Buffer payload;
CreatePayload(4 * kPagesPerBlock, &payload);
auto result = data_sink_->WriteFirmware(fuchsia_paver::wire::Configuration::kB,
fidl::StringView::FromExternal(kFirmwareTypeBootloader),
std::move(payload));
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_status());
ASSERT_OK(result->result.status());
ValidateWritten(kBootloaderFirstBlock, 4);
WriteData(kBootloaderFirstBlock, 4 * kPagesPerBlock, 0xff);
}
TEST_F(PaverServiceSkipBlockTest, WriteFirmwareUnsupportedConfigR) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
ASSERT_NO_FATAL_FAILURES(FindDataSink());
fuchsia_mem::wire::Buffer payload;
CreatePayload(4 * kPagesPerBlock, &payload);
auto result = data_sink_->WriteFirmware(fuchsia_paver::wire::Configuration::kRecovery,
fidl::StringView::FromExternal(kFirmwareTypeBootloader),
std::move(payload));
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_unsupported());
ASSERT_TRUE(result->result.unsupported());
ValidateUnwritten(kBootloaderFirstBlock, 4);
}
TEST_F(PaverServiceSkipBlockTest, WriteFirmwareBl2ConfigASupported) {
// BL2 special handling: we should always leave the first 4096 bytes intact.
constexpr size_t kBl2StartByte = kBl2FirstBlock * kPageSize * kPagesPerBlock;
constexpr size_t kBl2SkipLength = 4096;
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
ASSERT_NO_FATAL_FAILURES(FindDataSink());
WriteDataBytes(kBl2StartByte, kBl2SkipLength, 0xC6);
fuchsia_mem::wire::Buffer payload;
CreatePayload(kBl2ImagePages, &payload);
auto result = data_sink_->WriteFirmware(fuchsia_paver::wire::Configuration::kA,
fidl::StringView::FromExternal(kFirmwareTypeBl2),
std::move(payload));
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_status());
ASSERT_OK(result->result.status());
}
TEST_F(PaverServiceSkipBlockTest, WriteFirmwareBl2UnsupportedConfigBFallBackToA) {
// BL2 special handling: we should always leave the first 4096 bytes intact.
constexpr size_t kBl2StartByte = kBl2FirstBlock * kPageSize * kPagesPerBlock;
constexpr size_t kBl2SkipLength = 4096;
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
WriteDataBytes(kBl2StartByte, kBl2SkipLength, 0xC6);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
fuchsia_mem::wire::Buffer payload;
CreatePayload(kBl2ImagePages, &payload);
auto result = data_sink_->WriteFirmware(fuchsia_paver::wire::Configuration::kB,
fidl::StringView::FromExternal(kFirmwareTypeBl2),
std::move(payload));
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_status());
ASSERT_OK(result->result.status());
}
TEST_F(PaverServiceSkipBlockTest, WriteFirmwareBl2UnsupportedConfigR) {
// BL2 special handling: we should always leave the first 4096 bytes intact.
constexpr size_t kBl2StartByte = kBl2FirstBlock * kPageSize * kPagesPerBlock;
constexpr size_t kBl2SkipLength = 4096;
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
WriteDataBytes(kBl2StartByte, kBl2SkipLength, 0xC6);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
fuchsia_mem::wire::Buffer payload;
CreatePayload(kBl2ImagePages, &payload);
auto result = data_sink_->WriteFirmware(fuchsia_paver::wire::Configuration::kRecovery,
fidl::StringView::FromExternal(kFirmwareTypeBl2),
std::move(payload));
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_unsupported());
ASSERT_TRUE(result->result.unsupported());
}
TEST_F(PaverServiceSkipBlockTest, WriteFirmwareUnsupportedType) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
constexpr fuchsia_paver::wire::Configuration kAllConfigs[] = {
fuchsia_paver::wire::Configuration::kA,
fuchsia_paver::wire::Configuration::kB,
fuchsia_paver::wire::Configuration::kRecovery,
};
ASSERT_NO_FATAL_FAILURES(FindDataSink());
for (auto config : kAllConfigs) {
fuchsia_mem::wire::Buffer payload;
CreatePayload(4 * kPagesPerBlock, &payload);
auto result = data_sink_->WriteFirmware(
config, fidl::StringView::FromExternal(kFirmwareTypeUnsupported), std::move(payload));
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_unsupported());
ASSERT_TRUE(result->result.unsupported());
ValidateUnwritten(kBootloaderFirstBlock, 4);
ValidateUnwritten(kBl2FirstBlock, 1);
}
}
TEST_F(PaverServiceSkipBlockTest, WriteFirmwareError) {
// Make a RAM NAND device without a visible "bootloader" partition so that
// the partitioner initializes properly but then fails when trying to find it.
fuchsia_hardware_nand_RamNandInfo info = kNandInfo;
info.partition_map.partitions[1].hidden = true;
ASSERT_NO_FATAL_FAILURES(InitializeRamNand(info));
ASSERT_NO_FATAL_FAILURES(FindDataSink());
fuchsia_mem::wire::Buffer payload;
CreatePayload(4 * kPagesPerBlock, &payload);
auto result = data_sink_->WriteFirmware(fuchsia_paver::wire::Configuration::kA,
fidl::StringView::FromExternal(kFirmwareTypeBootloader),
std::move(payload));
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_status());
ASSERT_NOT_OK(result->result.status());
ValidateUnwritten(kBootloaderFirstBlock, 4);
}
TEST_F(PaverServiceSkipBlockTest, ReadAssetKernelConfigA) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
WriteData(8 * kPagesPerBlock, 2 * kPagesPerBlock, 0x4a);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->ReadAsset(fuchsia_paver::wire::Configuration::kA,
fuchsia_paver::wire::Asset::kKernel);
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ValidateWritten(result->result.response().asset, 2 * kPagesPerBlock);
}
TEST_F(PaverServiceSkipBlockTest, ReadAssetKernelConfigB) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
WriteData(10 * kPagesPerBlock, 2 * kPagesPerBlock, 0x4a);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->ReadAsset(fuchsia_paver::wire::Configuration::kB,
fuchsia_paver::wire::Asset::kKernel);
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ValidateWritten(result->result.response().asset, 2 * kPagesPerBlock);
}
TEST_F(PaverServiceSkipBlockTest, ReadAssetKernelConfigRecovery) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
WriteData(12 * kPagesPerBlock, 2 * kPagesPerBlock, 0x4a);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->ReadAsset(fuchsia_paver::wire::Configuration::kRecovery,
fuchsia_paver::wire::Asset::kKernel);
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ValidateWritten(result->result.response().asset, 2 * kPagesPerBlock);
}
TEST_F(PaverServiceSkipBlockTest, ReadAssetVbMetaConfigA) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
WriteData(14 * kPagesPerBlock + 32, 32, 0x4a);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->ReadAsset(fuchsia_paver::wire::Configuration::kA,
fuchsia_paver::wire::Asset::kVerifiedBootMetadata);
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ValidateWritten(result->result.response().asset, 32);
}
TEST_F(PaverServiceSkipBlockTest, ReadAssetVbMetaConfigB) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
WriteData(14 * kPagesPerBlock + 64, 32, 0x4a);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->ReadAsset(fuchsia_paver::wire::Configuration::kB,
fuchsia_paver::wire::Asset::kVerifiedBootMetadata);
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ValidateWritten(result->result.response().asset, 32);
}
TEST_F(PaverServiceSkipBlockTest, ReadAssetVbMetaConfigRecovery) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
WriteData(14 * kPagesPerBlock + 96, 32, 0x4a);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->ReadAsset(fuchsia_paver::wire::Configuration::kRecovery,
fuchsia_paver::wire::Asset::kVerifiedBootMetadata);
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ValidateWritten(result->result.response().asset, 32);
}
TEST_F(PaverServiceSkipBlockTest, ReadAssetZbiSize) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
zbi_header_t container;
container.type = ZBI_TYPE_CONTAINER;
container.extra = ZBI_CONTAINER_MAGIC;
container.magic = ZBI_ITEM_MAGIC;
container.flags = ZBI_FLAG_VERSION;
container.crc32 = ZBI_ITEM_NO_CRC32;
container.length = sizeof(zbi_header_t);
WriteDataBytes(8 * kPagesPerBlock * kPageSize, &container, sizeof(container));
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->ReadAsset(fuchsia_paver::wire::Configuration::kA,
fuchsia_paver::wire::Asset::kKernel);
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().asset.size, sizeof(container));
}
TEST_F(PaverServiceSkipBlockTest, WriteBootloader) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fuchsia_mem::wire::Buffer payload;
CreatePayload(4 * kPagesPerBlock, &payload);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->WriteBootloader(std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
ValidateWritten(4, 4);
}
// We prefill the bootloader partition with the expected data, leaving the last block as 0xFF.
// Normally the last page would be overwritten with 0s, but because the actual payload is identical,
// we don't actually pave the image, so the extra page stays as 0xFF.
TEST_F(PaverServiceSkipBlockTest, WriteBootloaderNotAligned) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
fuchsia_mem::wire::Buffer payload;
CreatePayload(4 * kPagesPerBlock - 1, &payload);
WriteData(4 * kPagesPerBlock, 4 * kPagesPerBlock - 1, 0x4a);
WriteData(8 * kPagesPerBlock - 1, 1, 0xff);
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->WriteBootloader(std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
ValidateWrittenPages(4 * kPagesPerBlock, 4 * kPagesPerBlock - 1);
ValidateUnwrittenPages(8 * kPagesPerBlock - 1, 1);
}
TEST_F(PaverServiceSkipBlockTest, WriteDataFile) {
// TODO(fxbug.dev/33793): Figure out a way to test this.
}
TEST_F(PaverServiceSkipBlockTest, WriteVolumes) {
// TODO(fxbug.dev/33793): Figure out a way to test this.
}
TEST_F(PaverServiceSkipBlockTest, WipeVolumeEmptyFvm) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->WipeVolume();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_TRUE(result->result.response().volume);
}
void CheckGuid(const fbl::unique_fd& device, const uint8_t type[GPT_GUID_LEN]) {
fdio_cpp::UnownedFdioCaller caller(device.get());
auto result = fidl::WireCall<partition::Partition>(caller.channel()).GetTypeGuid();
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
auto* guid = result.value().guid.get();
EXPECT_BYTES_EQ(type, guid, GPT_GUID_LEN);
}
TEST_F(PaverServiceSkipBlockTest, WipeVolumeCreatesFvm) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
constexpr size_t kBufferSize = 8192;
char buffer[kBufferSize];
memset(buffer, 'a', kBufferSize);
EXPECT_EQ(kBufferSize, pwrite(fvm_.get(), buffer, kBufferSize, 0));
ASSERT_NO_FATAL_FAILURES(FindDataSink());
auto result = data_sink_->WipeVolume();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_TRUE(result->result.response().volume);
EXPECT_EQ(kBufferSize, pread(fvm_.get(), buffer, kBufferSize, 0));
EXPECT_BYTES_EQ(fvm_magic, buffer, sizeof(fvm_magic));
fidl::ClientEnd<fuchsia_hardware_block_volume::VolumeManager> volume_client =
std::move(result->result.mutable_response().volume);
// This force-casts the protocol type from
// |fuchsia.hardware.block.volume/VolumeManager| into
// |fuchsia.device/Controller|. It only works because protocols hosted
// on devfs are automatically multiplexed with both the
// |fuchsia.device/Controller| and the |fuchsia.io/File| protocol.
fidl::ClientEnd<fuchsia_device::Controller> device_client(std::move(volume_client.channel()));
std::string path = storage::GetTopologicalPath(device_client).value().substr(5); // strip "/dev/"
ASSERT_FALSE(path.empty());
std::string blob_path = path + "/blobfs-p-1/block";
fbl::unique_fd blob_device(openat(device_->devfs_root().get(), blob_path.c_str(), O_RDONLY));
ASSERT_TRUE(blob_device);
constexpr uint8_t kBlobType[GPT_GUID_LEN] = GUID_BLOB_VALUE;
ASSERT_NO_FAILURES(CheckGuid(blob_device, kBlobType));
uint8_t kEmptyData[kBufferSize];
memset(kEmptyData, 0xff, kBufferSize);
EXPECT_EQ(kBufferSize, pread(blob_device.get(), buffer, kBufferSize, 0));
EXPECT_BYTES_EQ(kEmptyData, buffer, kBufferSize);
std::string data_path = path + "/minfs-p-2/block";
fbl::unique_fd data_device(openat(device_->devfs_root().get(), data_path.c_str(), O_RDONLY));
ASSERT_TRUE(data_device);
constexpr uint8_t kDataType[GPT_GUID_LEN] = GUID_DATA_VALUE;
ASSERT_NO_FAILURES(CheckGuid(data_device, kDataType));
EXPECT_EQ(kBufferSize, pread(data_device.get(), buffer, kBufferSize, 0));
EXPECT_BYTES_EQ(kEmptyData, buffer, kBufferSize);
}
void PaverServiceSkipBlockTest::TestSysconfigWriteBufferedClient(uint32_t offset_in_pages,
uint32_t sysconfig_pages) {
{
auto result = sysconfig_->GetPartitionSize();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_EQ(result->result.response().size, sysconfig_pages * kPageSize);
}
{
fuchsia_mem::wire::Buffer payload;
CreatePayload(sysconfig_pages, &payload);
auto result = sysconfig_->Write(std::move(payload));
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
// Without flushing, data in the storage should remain unchanged.
ASSERT_NO_FATAL_FAILURES(
ValidateUnwrittenPages(14 * kPagesPerBlock + offset_in_pages, sysconfig_pages));
}
{
auto result = sysconfig_->Flush();
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
ASSERT_NO_FATAL_FAILURES(
ValidateWrittenPages(14 * kPagesPerBlock + offset_in_pages, sysconfig_pages));
}
{
// Validate read.
auto result = sysconfig_->Read();
ASSERT_OK(result.status());
ASSERT_TRUE(result->result.is_response());
ASSERT_NO_FATAL_FAILURES(ValidateWritten(result->result.response().data, sysconfig_pages));
}
}
TEST_F(PaverServiceSkipBlockTest, SysconfigWriteWithBufferredClientLayoutNotUpdated) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
// Enable write-caching + abr metadata wear-leveling
fake_svc_.fake_boot_args().SetAstroSysConfigAbrWearLeveling(true);
ASSERT_NO_FATAL_FAILURES(FindSysconfig());
ASSERT_NO_FATAL_FAILURES(TestSysconfigWriteBufferedClient(0, 15 * 2));
}
TEST_F(PaverServiceSkipBlockTest, SysconfigWriteWithBufferredClientLayoutUpdated) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
// Enable write-caching + abr metadata wear-leveling
fake_svc_.fake_boot_args().SetAstroSysConfigAbrWearLeveling(true);
auto abr_data = GetAbrWearlevelingSupportingLayout();
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindSysconfig());
ASSERT_NO_FATAL_FAILURES(TestSysconfigWriteBufferedClient(2, 5 * 2));
}
void PaverServiceSkipBlockTest::TestSysconfigWipeBufferedClient(uint32_t offset_in_pages,
uint32_t sysconfig_pages) {
{
auto result = sysconfig_->Wipe();
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
// Without flushing, data in the storage should remain unchanged.
ASSERT_NO_FATAL_FAILURES(
ValidateUnwrittenPages(14 * kPagesPerBlock + offset_in_pages, sysconfig_pages));
}
{
auto result = sysconfig_->Flush();
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
ASSERT_NO_FATAL_FAILURES(AssertContents(14 * kSkipBlockSize + offset_in_pages * kPageSize,
sysconfig_pages * kPageSize, 0));
}
}
TEST_F(PaverServiceSkipBlockTest, SysconfigWipeWithBufferredClientLayoutNotUpdated) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
// Enable write-caching + abr metadata wear-leveling
fake_svc_.fake_boot_args().SetAstroSysConfigAbrWearLeveling(true);
ASSERT_NO_FATAL_FAILURES(FindSysconfig());
ASSERT_NO_FATAL_FAILURES(TestSysconfigWipeBufferedClient(0, 15 * 2));
}
TEST_F(PaverServiceSkipBlockTest, SysconfigWipeWithBufferredClientLayoutUpdated) {
ASSERT_NO_FATAL_FAILURES(InitializeRamNand());
// Enable write-caching + abr metadata wear-leveling
fake_svc_.fake_boot_args().SetAstroSysConfigAbrWearLeveling(true);
auto abr_data = GetAbrWearlevelingSupportingLayout();
SetAbr(abr_data);
ASSERT_NO_FATAL_FAILURES(FindSysconfig());
ASSERT_NO_FATAL_FAILURES(TestSysconfigWipeBufferedClient(2, 5 * 2));
}
constexpr uint8_t kEmptyType[GPT_GUID_LEN] = GUID_EMPTY_VALUE;
#if defined(__x86_64__)
class PaverServiceBlockTest : public PaverServiceTest {
public:
PaverServiceBlockTest() { ASSERT_NO_FATAL_FAILURES(SpawnIsolatedDevmgr()); }
protected:
void SpawnIsolatedDevmgr() {
devmgr_launcher::Args args;
args.sys_device_driver = IsolatedDevmgr::kSysdevDriver;
args.driver_search_paths.push_back("/boot/driver");
args.disable_block_watcher = false;
ASSERT_OK(IsolatedDevmgr::Create(std::move(args), &devmgr_));
// Forward the block watcher FIDL interface from the devmgr.
fake_svc_.ForwardServiceTo(fidl::DiscoverableProtocolName<fuchsia_fshost::BlockWatcher>,
devmgr_.fshost_outgoing_dir());
fbl::unique_fd fd;
ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root(), "misc/ramctl", &fd));
static_cast<paver::Paver*>(provider_ctx_)->set_devfs_root(devmgr_.devfs_root().duplicate());
static_cast<paver::Paver*>(provider_ctx_)->set_svc_root(std::move(fake_svc_.svc_chan()));
}
void UseBlockDevice(zx::channel block_device) {
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
auto result = client_->UseBlockDevice(std::move(block_device), std::move(remote));
ASSERT_OK(result.status());
data_sink_.emplace(std::move(local));
}
IsolatedDevmgr devmgr_;
std::optional<fidl::WireSyncClient<fuchsia_paver::DynamicDataSink>> data_sink_;
};
TEST_F(PaverServiceBlockTest, DISABLED_InitializePartitionTables) {
std::unique_ptr<BlockDevice> gpt_dev;
// 32GiB disk.
constexpr uint64_t block_count = (32LU << 30) / kBlockSize;
ASSERT_NO_FATAL_FAILURES(
BlockDevice::Create(devmgr_.devfs_root(), kEmptyType, block_count, &gpt_dev));
zx::channel gpt_chan;
ASSERT_OK(fdio_fd_clone(gpt_dev->fd(), gpt_chan.reset_and_get_address()));
ASSERT_NO_FATAL_FAILURES(UseBlockDevice(std::move(gpt_chan)));
auto result = data_sink_->InitializePartitionTables();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
TEST_F(PaverServiceBlockTest, DISABLED_InitializePartitionTablesMultipleDevices) {
std::unique_ptr<BlockDevice> gpt_dev1, gpt_dev2;
// 32GiB disk.
constexpr uint64_t block_count = (32LU << 30) / kBlockSize;
ASSERT_NO_FATAL_FAILURES(
BlockDevice::Create(devmgr_.devfs_root(), kEmptyType, block_count, &gpt_dev1));
ASSERT_NO_FATAL_FAILURES(
BlockDevice::Create(devmgr_.devfs_root(), kEmptyType, block_count, &gpt_dev2));
zx::channel gpt_chan;
ASSERT_OK(fdio_fd_clone(gpt_dev1->fd(), gpt_chan.reset_and_get_address()));
ASSERT_NO_FATAL_FAILURES(UseBlockDevice(std::move(gpt_chan)));
auto result = data_sink_->InitializePartitionTables();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
}
TEST_F(PaverServiceBlockTest, DISABLED_WipePartitionTables) {
std::unique_ptr<BlockDevice> gpt_dev;
// 32GiB disk.
constexpr uint64_t block_count = (32LU << 30) / kBlockSize;
ASSERT_NO_FATAL_FAILURES(
BlockDevice::Create(devmgr_.devfs_root(), kEmptyType, block_count, &gpt_dev));
zx::channel gpt_chan;
ASSERT_OK(fdio_fd_clone(gpt_dev->fd(), gpt_chan.reset_and_get_address()));
ASSERT_NO_FATAL_FAILURES(UseBlockDevice(std::move(gpt_chan)));
auto result = data_sink_->InitializePartitionTables();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
auto wipe_result = data_sink_->WipePartitionTables();
ASSERT_OK(wipe_result.status());
ASSERT_OK(wipe_result->status);
}
TEST_F(PaverServiceBlockTest, DISABLED_WipeVolume) {
std::unique_ptr<BlockDevice> gpt_dev;
// 32GiB disk.
constexpr uint64_t block_count = (32LU << 30) / kBlockSize;
ASSERT_NO_FATAL_FAILURES(
BlockDevice::Create(devmgr_.devfs_root(), kEmptyType, block_count, &gpt_dev));
zx::channel gpt_chan;
ASSERT_OK(fdio_fd_clone(gpt_dev->fd(), gpt_chan.reset_and_get_address()));
ASSERT_NO_FATAL_FAILURES(UseBlockDevice(std::move(gpt_chan)));
auto result = data_sink_->InitializePartitionTables();
ASSERT_OK(result.status());
ASSERT_OK(result->status);
auto wipe_result = data_sink_->WipeVolume();
ASSERT_OK(wipe_result.status());
ASSERT_FALSE(wipe_result->result.is_err());
}
#endif
class PaverServiceGptDeviceTest : public PaverServiceTest {
protected:
void SpawnIsolatedDevmgr(const char* board_name) {
driver_integration_test::IsolatedDevmgr::Args args;
args.driver_search_paths.push_back("/boot/driver");
args.disable_block_watcher = false;
args.board_name = board_name;
ASSERT_OK(driver_integration_test::IsolatedDevmgr::Create(&args, &devmgr_));
// Forward the block watcher FIDL interface from the devmgr.
fake_svc_.ForwardServiceTo(fidl::DiscoverableProtocolName<fuchsia_fshost::BlockWatcher>,
devmgr_.fshost_outgoing_dir());
fbl::unique_fd fd;
ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root(), "misc/ramctl", &fd));
ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root(), "sys/platform", &fd));
static_cast<paver::Paver*>(provider_ctx_)->set_dispatcher(loop_.dispatcher());
static_cast<paver::Paver*>(provider_ctx_)->set_devfs_root(devmgr_.devfs_root().duplicate());
fidl::ClientEnd<fuchsia_io::Directory> svc_root = GetSvcRoot();
static_cast<paver::Paver*>(provider_ctx_)->set_svc_root(std::move(svc_root));
}
void InitializeGptDevice(const char* board_name, uint64_t block_count, uint32_t block_size) {
SpawnIsolatedDevmgr(board_name);
block_count_ = block_count;
block_size_ = block_size;
ASSERT_NO_FATAL_FAILURES(
BlockDevice::Create(devmgr_.devfs_root(), kEmptyType, block_count, block_size, &gpt_dev_));
}
fidl::ClientEnd<fuchsia_io::Directory> GetSvcRoot() {
return service::MaybeClone(fake_svc_.svc_chan());
}
struct PartitionDescription {
const char* name;
const uint8_t* type;
uint64_t start;
uint64_t length;
};
void InitializeStartingGPTPartitions(const std::vector<PartitionDescription>& init_partitions) {
// Pause the block watcher while we write partitions to the disk.
// This is to avoid the block watcher seeing an intermediate state of the partition table
// and incorrectly treating it as an MBR.
// The watcher is automatically resumed when this goes out of scope.
auto pauser = paver::BlockWatcherPauser::Create(GetSvcRoot());
ASSERT_OK(pauser);
std::unique_ptr<gpt::GptDevice> gpt;
ASSERT_OK(gpt::GptDevice::Create(gpt_dev_->fd(), gpt_dev_->block_size(),
gpt_dev_->block_count(), &gpt));
ASSERT_OK(gpt->Sync());
for (const auto& part : init_partitions) {
ASSERT_OK(
gpt->AddPartition(part.name, part.type, GetRandomGuid(), part.start, part.length, 0),
"%s", part.name);
}
ASSERT_OK(gpt->Sync());
fdio_cpp::UnownedFdioCaller caller(gpt_dev_->fd());
auto result = fidl::WireCall<fuchsia_device::Controller>(caller.channel())
.Rebind(fidl::StringView("/boot/driver/gpt.so"));
ASSERT_TRUE(result.ok());
ASSERT_FALSE(result->result.is_err());
}
uint8_t* GetRandomGuid() {
static uint8_t random_guid[GPT_GUID_LEN];
zx_cprng_draw(random_guid, GPT_GUID_LEN);
return random_guid;
}
driver_integration_test::IsolatedDevmgr devmgr_;
std::unique_ptr<BlockDevice> gpt_dev_;
uint64_t block_count_;
uint64_t block_size_;
};
class PaverServiceLuisTest : public PaverServiceGptDeviceTest {
public:
PaverServiceLuisTest() { ASSERT_NO_FATAL_FAILURES(InitializeGptDevice("luis", 0x748034, 512)); }
void InitializeLuisGPTPartitions() {
constexpr uint8_t kDummyType[GPT_GUID_LEN] = {0xaf, 0x3d, 0xc6, 0x0f, 0x83, 0x84, 0x72, 0x47,
0x8e, 0x79, 0x3d, 0x69, 0xd8, 0x47, 0x7d, 0xe4};
const std::vector<PartitionDescription> kLuisStartingPartitions = {
{GPT_DURABLE_BOOT_NAME, kDummyType, 0x10400, 0x10000},
};
ASSERT_NO_FATAL_FAILURES(InitializeStartingGPTPartitions(kLuisStartingPartitions));
}
};
TEST_F(PaverServiceLuisTest, CreateAbr) {
ASSERT_NO_FATAL_FAILURES(InitializeLuisGPTPartitions());
std::shared_ptr<paver::Context> context;
fidl::ClientEnd<fuchsia_io::Directory> svc_root = GetSvcRoot();
EXPECT_OK(
abr::ClientFactory::Create(devmgr_.devfs_root().duplicate(), std::move(svc_root), context));
}
TEST_F(PaverServiceLuisTest, SysconfigNotSupportedAndFailWithPeerClosed) {
ASSERT_NO_FATAL_FAILURES(InitializeLuisGPTPartitions());
zx::channel sysconfig_local, sysconfig_remote;
ASSERT_OK(zx::channel::create(0, &sysconfig_local, &sysconfig_remote));
auto result = client_->FindSysconfig(std::move(sysconfig_remote));
ASSERT_OK(result.status());
fidl::WireSyncClient<fuchsia_paver::Sysconfig> sysconfig(std::move(sysconfig_local));
auto wipe_result = sysconfig.Wipe();
ASSERT_EQ(wipe_result.status(), ZX_ERR_PEER_CLOSED);
}
} // namespace