blob: a025f16b82337a623cda4ac4209d595044d4c87e [file] [log] [blame]
// Copyright 2023 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 "bootpart.h"
#include <lib/driver/compat/cpp/banjo_client.h>
#include <lib/driver/metadata/cpp/metadata_server.h>
#include <lib/driver/testing/cpp/driver_test.h>
#include <algorithm>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/lib/testing/predicates/status.h"
namespace bootpart {
class FakeBlockDevice : public ddk::BlockImplProtocol<FakeBlockDevice> {
public:
FakeBlockDevice() { memset(data_.data(), 0xff, sizeof(data_)); }
std::span<const char, 240> data() const { return std::span<const char, 240>(data_); }
bool flushed() const { return flushed_; }
void BlockImplQuery(block_info_t* out_info, uint64_t* out_block_op_size) {
out_info->block_count = 24;
out_info->block_size = 10;
out_info->max_transfer_size = 10;
out_info->flags = 0;
*out_block_op_size = sizeof(block_op_t);
}
void BlockImplQueue(block_op_t* txn, block_impl_queue_callback callback, void* cookie) {
zx_status_t status = ZX_ERR_NOT_SUPPORTED;
switch (txn->command.opcode) {
case BLOCK_OPCODE_READ: {
static uint64_t expected_lba = 0;
if (txn->rw.length != 1 || txn->rw.offset_dev != expected_lba) {
status = ZX_ERR_OUT_OF_RANGE;
} else {
status = zx_vmo_write(txn->rw.vmo, data_.data() + (txn->rw.offset_dev * 10),
txn->rw.offset_vmo * 10, 10);
expected_lba += 12; // Expect next read at LBA == 12.
}
break;
}
case BLOCK_OPCODE_WRITE: {
static uint64_t expected_lba = 0;
if (txn->rw.length != 1 || txn->rw.offset_dev != expected_lba) {
status = ZX_ERR_OUT_OF_RANGE;
} else {
status = zx_vmo_read(txn->rw.vmo, data_.data() + (txn->rw.offset_dev * 10),
txn->rw.offset_vmo * 10, 10);
flushed_ = status != ZX_OK && flushed_;
expected_lba += 12; // Expect next write at LBA == 12.
}
break;
}
case BLOCK_OPCODE_FLUSH:
flushed_ = true;
status = ZX_OK;
break;
default:
break;
}
callback(cookie, status, txn);
}
compat::DeviceServer::BanjoConfig GetBanjoConfig() {
compat::DeviceServer::BanjoConfig config{.default_proto_id = ZX_PROTOCOL_BLOCK_IMPL};
config.callbacks[ZX_PROTOCOL_BLOCK_IMPL] = banjo_server_.callback();
return config;
}
private:
std::array<char, 240> data_; // 24 blocks of 10 bytes each.
bool flushed_ = true;
compat::BanjoServer banjo_server_{ZX_PROTOCOL_BLOCK_IMPL, this, &block_impl_protocol_ops_};
};
const std::string kLongName = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
class BootpartTestEnvironment : public fdf_testing::Environment {
public:
void Init(fuchsia_boot_metadata::PartitionMap partition_map) {
device_server_.Initialize("default", std::nullopt, block_device_.GetBanjoConfig());
partition_map_ = std::move(partition_map);
}
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override {
async_dispatcher_t* dispatcher = fdf::Dispatcher::GetCurrent()->async_dispatcher();
EXPECT_OK(device_server_.Serve(dispatcher, &to_driver_vfs));
EXPECT_OK(partition_map_metadata_server_.Serve(to_driver_vfs, dispatcher, partition_map_));
return zx::ok();
}
const FakeBlockDevice& block_device() const { return block_device_; }
private:
FakeBlockDevice block_device_;
compat::DeviceServer device_server_;
fdf_metadata::MetadataServer<fuchsia_boot_metadata::PartitionMap> partition_map_metadata_server_;
fuchsia_boot_metadata::PartitionMap partition_map_;
};
class FixtureConfig final {
public:
using DriverType = Driver;
using EnvironmentType = BootpartTestEnvironment;
};
class BootPartitionTest : public ::testing::Test {
public:
static const fuchsia_boot_metadata::PartitionMap kPartitionMap;
void SetUp() override {
driver_test_.RunInEnvironmentTypeContext([&](auto& env) { env.Init(kPartitionMap); });
ASSERT_OK(driver_test_.StartDriver());
}
void TearDown() override { ASSERT_OK(driver_test_.StopDriver()); }
protected:
Driver& driver() { return *driver_test_.driver(); }
template <typename BanjoClient>
BanjoClient ConnectToBanjo(size_t partition_index) {
static const uint64_t kProcessKoid = compat::internal::GetKoid();
const std::string instance = std::format("part-{:03}", partition_index);
zx::result compat_client_end =
driver_test_.Connect<fuchsia_driver_compat::Service::Device>(instance);
EXPECT_OK(compat_client_end);
fidl::WireClient<fuchsia_driver_compat::Device> compat(
std::move(compat_client_end.value()),
driver_test_.runtime().GetForegroundDispatcher()->async_dispatcher());
zx::result<BanjoClient> banjo_client;
compat->GetBanjoProtocol(BanjoClient::kProtocolId, kProcessKoid)
.ThenExactlyOnce(
[&](fidl::WireUnownedResult<fuchsia_driver_compat::Device::GetBanjoProtocol>& result) {
ASSERT_OK(result.status());
banjo_client = compat::internal::OnResult<BanjoClient>(result);
driver_test_.runtime().Quit();
});
driver_test_.runtime().Run();
EXPECT_OK(banjo_client);
EXPECT_TRUE(banjo_client.value().is_valid());
return banjo_client.value();
}
fdf_testing::ForegroundDriverTest<FixtureConfig>& driver_test() { return driver_test_; }
private:
fdf_testing::ForegroundDriverTest<FixtureConfig> driver_test_;
};
const fuchsia_boot_metadata::PartitionMap BootPartitionTest::kPartitionMap = {
{.partitions = std::vector<fuchsia_boot_metadata::Partition>{
{{
.type_guid = {'T', 'T', 'T', 'T', 'T', 'T', 'T', 'T', 'T', 'T', 'T', 'T', 'T', 'T',
'T', 'T'},
.unique_guid = {'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I',
'I', 'I'},
.first_block = 0,
.last_block = 11,
.name = "This is partition 0",
}},
{{
.type_guid = {'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U',
'U', 'U'},
.unique_guid = {'J', 'J', 'J', 'J', 'J', 'J', 'J', 'J', 'J', 'J', 'J', 'J', 'J', 'J',
'J', 'J'},
.first_block = 12,
.last_block = 23,
.name = kLongName,
}},
}}};
TEST_F(BootPartitionTest, BlockPartitionOps) {
std::span<const fuchsia_boot_metadata::Partition> partitions = kPartitionMap.partitions().value();
for (size_t i = 0; i < partitions.size(); ++i) {
const fuchsia_boot_metadata::Partition& partition = partitions[i];
ddk::BlockPartitionProtocolClient partition_client =
ConnectToBanjo<ddk::BlockPartitionProtocolClient>(i);
guid_t guid_type{};
EXPECT_OK(partition_client.GetGuid(GUIDTYPE_TYPE, &guid_type));
for (size_t j = 0; j < GUID_LENGTH; j++) {
EXPECT_EQ(reinterpret_cast<char*>(&guid_type)[j], partition.type_guid()[j]);
}
guid_t guid_instance{};
EXPECT_OK(partition_client.GetGuid(GUIDTYPE_INSTANCE, &guid_instance));
for (uint32_t j = 0; j < GUID_LENGTH; j++) {
EXPECT_EQ(reinterpret_cast<char*>(&guid_instance)[j], partition.unique_guid()[j]);
}
char name[MAX_PARTITION_NAME_LENGTH];
EXPECT_OK(partition_client.GetName(name, sizeof(name)));
EXPECT_EQ(std::string(name), partition.name());
char name_short[33];
EXPECT_OK(partition_client.GetName(name_short, 33));
EXPECT_EQ(std::string(name_short), partition.name());
EXPECT_NE(partition_client.GetName(name_short, 32), ZX_OK);
}
}
TEST_F(BootPartitionTest, BlockImplOpsPassedThrough) {
std::span<const fuchsia_boot_metadata::Partition> partitions = kPartitionMap.partitions().value();
for (size_t i = 0; i < partitions.size(); ++i) {
ddk::BlockImplProtocolClient block_client = ConnectToBanjo<ddk::BlockImplProtocolClient>(i);
block_info_t info{};
uint64_t block_op_size = 0;
block_client.Query(&info, &block_op_size);
EXPECT_EQ(info.block_count, 12u);
EXPECT_EQ(info.block_size, 10u);
EXPECT_EQ(info.max_transfer_size, 10u);
EXPECT_EQ(block_op_size, sizeof(block_op_t));
auto block_callback = [](void*, zx_status_t status, block_op_t*) { EXPECT_OK(status); };
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(10, 0, &vmo));
char buffer[10];
strncpy(buffer, "Test data", sizeof(buffer));
EXPECT_OK(vmo.write(buffer, 0, sizeof(buffer)));
block_op_t txn{
.rw =
{
.command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0},
.vmo = vmo.get(),
.length = 1,
.offset_dev = 0,
.offset_vmo = 0,
},
};
block_client.Queue(&txn, block_callback, nullptr);
driver_test().RunInEnvironmentTypeContext([](BootpartTestEnvironment& env) {
EXPECT_FALSE(env.block_device().flushed()); // FakeBlockDevice operates synchronously.
});
txn = {
.command = {.opcode = BLOCK_OPCODE_FLUSH, .flags = 0},
};
block_client.Queue(&txn, block_callback, nullptr);
driver_test().RunInEnvironmentTypeContext([](BootpartTestEnvironment& env) {
EXPECT_TRUE(env.block_device().flushed()); // FakeBlockDevice operates synchronously.
std::span data = env.block_device().data();
auto end = std::ranges::find(data, '\0');
ASSERT_NE(end, data.end());
EXPECT_EQ(std::string(data.begin(), end), "Test data");
});
txn = {
.rw =
{
.command = {.opcode = BLOCK_OPCODE_READ, .flags = 0},
.vmo = vmo.get(),
.length = 1,
.offset_dev = 0,
.offset_vmo = 0,
},
};
block_client.Queue(&txn, block_callback, nullptr);
EXPECT_OK(vmo.read(buffer, 0, sizeof(buffer)));
EXPECT_STREQ(buffer, "Test data");
}
}
} // namespace bootpart