| // 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 |