blob: 53f254a9e1d80f6c8d3cc96efbad115d29c9e228 [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 "lib/abr/abr.h"
#include <endian.h>
#include <fuchsia/device/llcpp/fidl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/cksum.h>
#include <lib/devmgr-integration-test/fixture.h>
#include <lib/driver-integration-test/fixture.h>
#include <lib/fdio/directory.h>
#include <lib/service/llcpp/service.h>
#include <zircon/hw/gpt.h>
#include <iostream>
#include <chromeos-disk-setup/chromeos-disk-setup.h>
#include <mock-boot-arguments/server.h>
#include <zxtest/zxtest.h>
#include "gpt/cros.h"
#include "src/lib/storage/vfs/cpp/pseudo_dir.h"
#include "src/lib/storage/vfs/cpp/synchronous_vfs.h"
#include "src/lib/uuid/uuid.h"
#include "src/storage/lib/paver/abr-client-vboot.h"
#include "src/storage/lib/paver/abr-client.h"
#include "src/storage/lib/paver/astro.h"
#include "src/storage/lib/paver/chromebook-x64.h"
#include "src/storage/lib/paver/luis.h"
#include "src/storage/lib/paver/sherlock.h"
#include "src/storage/lib/paver/test/test-utils.h"
#include "src/storage/lib/paver/utils.h"
#include "src/storage/lib/paver/x64.h"
namespace {
using devmgr_integration_test::RecursiveWaitForFile;
using driver_integration_test::IsolatedDevmgr;
TEST(AstroAbrTests, CreateFails) {
IsolatedDevmgr devmgr;
IsolatedDevmgr::Args args;
args.driver_search_paths.push_back("/boot/driver");
args.disable_block_watcher = false;
args.board_name = "sherlock";
ASSERT_OK(IsolatedDevmgr::Create(&args, &devmgr));
fbl::unique_fd fd;
ASSERT_OK(RecursiveWaitForFile(devmgr.devfs_root(), "sys/platform", &fd));
fidl::ClientEnd<fuchsia_io::Directory> svc_root;
ASSERT_NOT_OK(
paver::AstroAbrClientFactory().New(devmgr.devfs_root().duplicate(), svc_root, nullptr));
}
TEST(SherlockAbrTests, CreateFails) {
IsolatedDevmgr devmgr;
IsolatedDevmgr::Args args;
args.driver_search_paths.push_back("/boot/driver");
args.disable_block_watcher = false;
args.board_name = "astro";
ASSERT_OK(IsolatedDevmgr::Create(&args, &devmgr));
fbl::unique_fd fd;
ASSERT_OK(RecursiveWaitForFile(devmgr.devfs_root(), "sys/platform", &fd));
auto svc_root = service::ConnectAt<fuchsia_io::Directory>(devmgr.fshost_outgoing_dir(), "svc");
ASSERT_OK(svc_root.status_value());
ASSERT_NOT_OK(paver::SherlockAbrClientFactory().Create(devmgr.devfs_root().duplicate(), *svc_root,
nullptr));
}
TEST(LuisAbrTests, CreateFails) {
IsolatedDevmgr devmgr;
IsolatedDevmgr::Args args;
args.driver_search_paths.push_back("/boot/driver");
args.disable_block_watcher = false;
args.board_name = "astro";
ASSERT_OK(IsolatedDevmgr::Create(&args, &devmgr));
fbl::unique_fd fd;
ASSERT_OK(RecursiveWaitForFile(devmgr.devfs_root(), "sys/platform", &fd));
auto svc_root = service::ConnectAt<fuchsia_io::Directory>(devmgr.fshost_outgoing_dir(), "svc");
ASSERT_OK(svc_root.status_value());
ASSERT_NOT_OK(
paver::LuisAbrClientFactory().Create(devmgr.devfs_root().duplicate(), *svc_root, nullptr));
}
TEST(X64AbrTests, CreateFails) {
IsolatedDevmgr devmgr;
IsolatedDevmgr::Args args;
args.driver_search_paths.push_back("/boot/driver");
args.disable_block_watcher = false;
args.board_name = "x64";
ASSERT_OK(IsolatedDevmgr::Create(&args, &devmgr));
fbl::unique_fd fd;
ASSERT_OK(RecursiveWaitForFile(devmgr.devfs_root(), "sys/platform", &fd));
auto svc_root = service::ConnectAt<fuchsia_io::Directory>(devmgr.fshost_outgoing_dir(), "svc");
ASSERT_OK(svc_root.status_value());
ASSERT_NOT_OK(
paver::X64AbrClientFactory().Create(devmgr.devfs_root().duplicate(), *svc_root, nullptr));
}
class ChromebookX64AbrTests : public zxtest::Test {
static constexpr int kBlockSize = 512;
static constexpr uint64_t kZxPartBlocks = SZ_ZX_PART / kBlockSize;
static constexpr uint64_t kMinFvmSize = (8LU * (1 << 30)) / kBlockSize;
static constexpr uint64_t kSysCfgSize = (1 << 20) / 512;
// we need at least 3 * SZ_ZX_PART for zircon a/b/r, and kMinFvmSize for fvm.
static constexpr uint64_t kDiskBlocks = 4 * kZxPartBlocks + kMinFvmSize;
static constexpr uint8_t kEmptyType[GPT_GUID_LEN] = GUID_EMPTY_VALUE;
static constexpr uint8_t kZirconType[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE;
static constexpr uint8_t kFvmType[GPT_GUID_LEN] = GUID_FVM_VALUE;
static constexpr uint8_t kSysCfgGuid[GPT_GUID_LEN] = GUID_SYS_CONFIG_VALUE;
protected:
ChromebookX64AbrTests()
: dispatcher_(&kAsyncLoopConfigNoAttachToCurrentThread),
dispatcher2_(&kAsyncLoopConfigAttachToCurrentThread),
fake_svc_(dispatcher_.dispatcher(), mock_boot_arguments::Server()) {
IsolatedDevmgr::Args args;
args.driver_search_paths.push_back("/boot/driver");
args.disable_block_watcher = false;
args.board_name = "chromebook-x64";
ASSERT_OK(IsolatedDevmgr::Create(&args, &devmgr_));
fbl::unique_fd fd;
ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root(), "sys/platform", &fd));
ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root(), "misc/ramctl", &fd));
ASSERT_NO_FATAL_FAILURES(
BlockDevice::Create(devmgr_.devfs_root(), kEmptyType, kDiskBlocks, kBlockSize, &disk_));
fake_svc_.fake_boot_args().GetArgumentsMap().emplace("zvb.current_slot", "_a");
dispatcher_.StartThread("abr-svc-test-loop");
dispatcher2_.StartThread("abr-svc-test-loop-2");
}
~ChromebookX64AbrTests() override { dispatcher_.Shutdown(); }
void SetupPartitions(AbrSlotIndex active_slot) {
std::unique_ptr<gpt::GptDevice> gpt;
ASSERT_OK(gpt::GptDevice::Create(disk_->fd(), /*blocksize=*/disk_->block_size(),
/*blocks=*/disk_->block_count(), &gpt));
ASSERT_OK(gpt->Sync());
// 2 (GPT header and MBR header) blocks + number of blocks in entry array.
uint64_t cur_start = 2 + gpt->EntryArrayBlockCount();
ASSERT_OK(gpt->AddPartition("zircon-a", kZirconType, kZirconType, cur_start, kZxPartBlocks, 0));
cur_start += kZxPartBlocks;
ASSERT_OK(gpt->AddPartition("zircon-b", kZirconType, kZirconType, cur_start, kZxPartBlocks, 0));
cur_start += kZxPartBlocks;
ASSERT_OK(gpt->AddPartition("zircon-r", kZirconType, kZirconType, cur_start, kZxPartBlocks, 0));
cur_start += kZxPartBlocks;
ASSERT_OK(gpt->AddPartition("fvm", kFvmType, kFvmType, cur_start, kMinFvmSize, 0));
cur_start += kMinFvmSize;
ASSERT_OK(gpt->AddPartition("syscfg", kSysCfgGuid, kSysCfgGuid, cur_start, kSysCfgSize, 0));
int active_partition = -1;
auto current_slot = "_x";
switch (active_slot) {
case kAbrSlotIndexA:
active_partition = 0;
current_slot = "_a";
break;
case kAbrSlotIndexB:
active_partition = 1;
current_slot = "_b";
break;
case kAbrSlotIndexR:
active_partition = 2;
current_slot = "_r";
break;
}
auto result = gpt->GetPartition(active_partition);
ASSERT_OK(result.status_value());
gpt_partition_t* part = *result;
gpt_cros_attr_set_priority(&part->flags, 15);
gpt_cros_attr_set_successful(&part->flags, true);
fake_svc_.fake_boot_args().GetArgumentsMap().emplace("zvb.current_slot", current_slot);
ASSERT_OK(gpt->Sync());
fdio_cpp::UnownedFdioCaller caller(disk_->fd());
auto result2 = fidl::WireCall<fuchsia_device::Controller>(caller.channel())
.Rebind(fidl::StringView("/boot/driver/gpt.so"));
ASSERT_TRUE(result2.ok());
ASSERT_FALSE(result2->result.is_err());
}
zx::status<std::unique_ptr<abr::Client>> GetAbrClient() {
auto& svc_root = fake_svc_.svc_chan();
return paver::ChromebookX64AbrClientFactory().New(devmgr_.devfs_root().duplicate(), svc_root,
nullptr);
}
std::unique_ptr<BlockDevice> disk_;
IsolatedDevmgr devmgr_;
async::Loop dispatcher_;
async::Loop dispatcher2_;
FakeSvc<mock_boot_arguments::Server> fake_svc_;
};
TEST_F(ChromebookX64AbrTests, CreateSucceeds) {
ASSERT_NO_FATAL_FAILURES(SetupPartitions(kAbrSlotIndexA));
auto client = GetAbrClient();
ASSERT_OK(client.status_value());
}
TEST_F(ChromebookX64AbrTests, QueryActiveSucceeds) {
ASSERT_NO_FATAL_FAILURES(SetupPartitions(kAbrSlotIndexA));
auto client = GetAbrClient();
ASSERT_OK(client.status_value());
bool marked_successful;
AbrSlotIndex slot = client->GetBootSlot(false, &marked_successful);
ASSERT_EQ(slot, kAbrSlotIndexA);
ASSERT_TRUE(marked_successful);
}
TEST_F(ChromebookX64AbrTests, GetSlotInfoSucceeds) {
ASSERT_NO_FATAL_FAILURES(SetupPartitions(kAbrSlotIndexB));
auto client = GetAbrClient();
ASSERT_OK(client.status_value());
auto info = client->GetSlotInfo(kAbrSlotIndexB);
ASSERT_OK(info.status_value());
ASSERT_TRUE(info->is_active);
ASSERT_TRUE(info->is_bootable);
ASSERT_TRUE(info->is_marked_successful);
ASSERT_EQ(info->num_tries_remaining, 0);
}
class CurrentSlotUuidTest : public zxtest::Test {
protected:
static constexpr int kBlockSize = 512;
static constexpr int kDiskBlocks = 1024;
static constexpr uint8_t kEmptyType[GPT_GUID_LEN] = GUID_EMPTY_VALUE;
static constexpr uint8_t kZirconType[GPT_GUID_LEN] = GPT_ZIRCON_ABR_TYPE_GUID;
static constexpr uint8_t kTestUuid[GPT_GUID_LEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
0xcc, 0xdd, 0xee, 0xff};
CurrentSlotUuidTest() {
IsolatedDevmgr::Args args;
args.driver_search_paths.push_back("/boot/driver");
args.disable_block_watcher = true;
ASSERT_OK(IsolatedDevmgr::Create(&args, &devmgr_));
fbl::unique_fd fd;
ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root(), "misc/ramctl", &fd));
ASSERT_NO_FATAL_FAILURES(
BlockDevice::Create(devmgr_.devfs_root(), kEmptyType, kDiskBlocks, kBlockSize, &disk_));
}
void CreateDiskWithPartition(const char* partition) {
ASSERT_OK(gpt::GptDevice::Create(disk_->fd(), /*blocksize=*/disk_->block_size(),
/*blocks=*/disk_->block_count(), &gpt_));
ASSERT_OK(gpt_->Sync());
ASSERT_OK(gpt_->AddPartition(partition, kZirconType, kTestUuid,
2 + gpt_->EntryArrayBlockCount(), 10, 0));
ASSERT_OK(gpt_->Sync());
fdio_cpp::UnownedFdioCaller caller(disk_->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());
}
fidl::ClientEnd<fuchsia_io::Directory> GetSvcRoot() {
auto fshost_root = devmgr_.fshost_outgoing_dir();
auto local = service::ConnectAt<fuchsia_io::Directory>(fshost_root, "svc");
if (!local.is_ok()) {
std::cout << "Failed to connect to fshost svc dir: " << local.status_string() << std::endl;
return fidl::ClientEnd<fuchsia_io::Directory>();
}
return std::move(*local);
}
IsolatedDevmgr devmgr_;
std::unique_ptr<BlockDevice> disk_;
std::unique_ptr<gpt::GptDevice> gpt_;
};
TEST_F(CurrentSlotUuidTest, TestZirconAIsSlotA) {
ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("zircon-a"));
auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kA);
}
TEST_F(CurrentSlotUuidTest, TestZirconAWithUnderscore) {
ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("zircon_a"));
auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kA);
}
TEST_F(CurrentSlotUuidTest, TestZirconAMixedCase) {
ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("ZiRcOn-A"));
auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kA);
}
TEST_F(CurrentSlotUuidTest, TestZirconB) {
ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("zircon_b"));
auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kB);
}
TEST_F(CurrentSlotUuidTest, TestZirconR) {
ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("ZIRCON-R"));
auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kRecovery);
}
TEST_F(CurrentSlotUuidTest, TestInvalid) {
ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("ZERCON-R"));
auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error_value(), ZX_ERR_NOT_SUPPORTED);
}
TEST(CurrentSlotTest, TestA) {
auto result = abr::CurrentSlotToConfiguration("_a");
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kA);
}
TEST(CurrentSlotTest, TestB) {
auto result = abr::CurrentSlotToConfiguration("_b");
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kB);
}
TEST(CurrentSlotTest, TestR) {
auto result = abr::CurrentSlotToConfiguration("_r");
ASSERT_TRUE(result.is_ok());
ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kRecovery);
}
TEST(CurrentSlotTest, TestInvalid) {
auto result = abr::CurrentSlotToConfiguration("_x");
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error_value(), ZX_ERR_NOT_SUPPORTED);
}
} // namespace