blob: cc8839da95bcdfacc4a04a2413f1e0fc56136d67 [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 <fuchsia/hardware/block/c/banjo.h>
#include <fuchsia/hardware/block/cpp/banjo.h>
#include <fuchsia/hardware/block/partition/cpp/banjo.h>
#include <inttypes.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <lib/zx/vmo.h>
#include <zircon/errors.h>
#include <zircon/hw/gpt.h>
#include <ddktl/device.h>
#include <fbl/alloc_checker.h>
#include <fbl/string.h>
#include <fbl/vector.h>
#include <gpt/c/gpt.h>
#include <zxtest/zxtest.h>
#include "gpt.h"
#include "gpt_test_data.h"
namespace gpt {
namespace {
// To make sure that we correctly convert UTF-16, to UTF-8, the second partition has a suffix with
// codepoint 0x10000, which in UTF-16 requires a surrogate pair.
constexpr std::string_view kPartition1Name = "Linux filesystem\xf0\x90\x80\x80";
class FakeBlockDevice : public ddk::BlockProtocol<FakeBlockDevice> {
public:
FakeBlockDevice() : proto_({&block_protocol_ops_, this}) {
info_.block_count = kBlockCnt;
info_.block_size = kBlockSz;
info_.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED;
}
block_protocol_t* proto() { return &proto_; }
void SetInfo(const block_info_t* info) { info_ = *info; }
void BlockQuery(block_info_t* info_out, size_t* block_op_size_out) {
*info_out = info_;
*block_op_size_out = sizeof(block_op_t);
}
void BlockQueue(block_op_t* operation, block_queue_callback completion_cb, void* cookie);
private:
zx_status_t BlockQueueOp(block_op_t* op);
block_protocol_t proto_{};
block_info_t info_{};
};
void FakeBlockDevice::BlockQueue(block_op_t* operation, block_queue_callback completion_cb,
void* cookie) {
zx_status_t status = BlockQueueOp(operation);
completion_cb(cookie, status, operation);
}
zx_status_t FakeBlockDevice::BlockQueueOp(block_op_t* op) {
const uint32_t command = op->command & BLOCK_OP_MASK;
const uint32_t bsize = info_.block_size;
if (command == BLOCK_OP_READ || command == BLOCK_OP_WRITE) {
if ((op->rw.offset_dev + op->rw.length) > (bsize * info_.block_count)) {
return ZX_ERR_OUT_OF_RANGE;
}
if (command == BLOCK_OP_WRITE) {
return ZX_OK;
}
} else if (command == BLOCK_OP_TRIM) {
if ((op->trim.offset_dev + op->trim.length) > (bsize * info_.block_count)) {
return ZX_ERR_OUT_OF_RANGE;
}
return ZX_OK;
} else if (command == BLOCK_OP_FLUSH) {
return ZX_OK;
} else {
return ZX_ERR_NOT_SUPPORTED;
}
size_t part_size = sizeof(test_partition_table);
size_t read_off = op->rw.offset_dev * bsize;
size_t read_len = op->rw.length * bsize;
if (read_len == 0) {
return ZX_OK;
}
uint64_t vmo_addr = op->rw.offset_vmo * bsize;
// Read initial part from header if in range.
if (read_off < part_size) {
size_t part_read_len = part_size - read_off;
if (part_read_len > read_len) {
part_read_len = read_len;
}
zx_vmo_write(op->rw.vmo, test_partition_table + read_off, vmo_addr, part_read_len);
read_len -= part_read_len;
read_off += part_read_len;
vmo_addr += part_read_len;
if (read_len == 0) {
return ZX_OK;
}
}
std::unique_ptr<uint8_t[]> zbuf(new uint8_t[bsize]);
memset(zbuf.get(), 0, bsize);
// Zero-fill remaining.
for (; read_len > 0; read_len -= bsize) {
zx_vmo_write(op->rw.vmo, zbuf.get(), vmo_addr, bsize);
vmo_addr += bsize;
}
return ZX_OK;
}
class GptDeviceTest : public zxtest::Test {
public:
GptDeviceTest() = default;
DISALLOW_COPY_ASSIGN_AND_MOVE(GptDeviceTest);
void SetInfo(const block_info_t* info) { fake_block_device_.SetInfo(info); }
void Init() { ddk_.SetProtocol(ZX_PROTOCOL_BLOCK, fake_block_device_.proto()); }
fake_ddk::Bind ddk_;
protected:
struct BlockOpResult {
sync_completion_t completion;
block_op_t op;
zx_status_t status;
};
static void BlockOpCompleter(void* cookie, zx_status_t status, block_op_t* bop) {
auto* result = static_cast<BlockOpResult*>(cookie);
result->status = status;
result->op = *bop;
sync_completion_signal(&result->completion);
}
private:
FakeBlockDevice fake_block_device_;
};
TEST_F(GptDeviceTest, DeviceTooSmall) {
Init();
const block_info_t info = {20, 512, BLOCK_MAX_TRANSFER_UNBOUNDED, 0, 0};
SetInfo(&info);
TableRef tab;
ASSERT_OK(PartitionTable::Create(fake_ddk::kFakeParent, &tab));
ASSERT_EQ(ZX_ERR_NO_SPACE, tab->Bind());
}
TEST_F(GptDeviceTest, DdkLifecycle) {
Init();
fbl::Vector<std::unique_ptr<PartitionDevice>> devices;
TableRef tab;
ASSERT_OK(PartitionTable::Create(fake_ddk::kFakeParent, &tab, &devices));
ASSERT_OK(tab->Bind());
ASSERT_EQ(devices.size(), 2);
char name[MAX_PARTITION_NAME_LENGTH];
guid_t guid;
// Device 0
PartitionDevice* dev0 = devices[0].get();
ASSERT_NOT_NULL(dev0);
ASSERT_OK(dev0->BlockPartitionGetName(name, sizeof(name)));
ASSERT_EQ(strcmp(name, "Linux filesystem"), 0);
ASSERT_OK(dev0->BlockPartitionGetGuid(GUIDTYPE_TYPE, &guid));
{
uint8_t expected_guid[GPT_GUID_LEN] = GUID_LINUX_FILESYSTEM;
EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN);
}
ASSERT_OK(dev0->BlockPartitionGetGuid(GUIDTYPE_INSTANCE, &guid));
{
uint8_t expected_guid[GPT_GUID_LEN] = GUID_UNIQUE_PART0;
EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN);
}
PartitionDevice* dev1 = devices[1].get();
ASSERT_NOT_NULL(dev1);
ASSERT_OK(dev1->BlockPartitionGetName(name, sizeof(name)));
ASSERT_EQ(kPartition1Name, name);
ASSERT_OK(dev1->BlockPartitionGetGuid(GUIDTYPE_TYPE, &guid));
{
uint8_t expected_guid[GPT_GUID_LEN] = GUID_LINUX_FILESYSTEM;
EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN);
}
ASSERT_OK(dev1->BlockPartitionGetGuid(GUIDTYPE_INSTANCE, &guid));
{
uint8_t expected_guid[GPT_GUID_LEN] = GUID_UNIQUE_PART1;
EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN);
}
dev0->AsyncRemove();
dev1->AsyncRemove();
EXPECT_TRUE(ddk_.Ok());
}
TEST_F(GptDeviceTest, GuidMapMetadata) {
Init();
fbl::Vector<std::unique_ptr<PartitionDevice>> devices;
const guid_map_t guid_map[] = {
{"Linux filesystem", GUID_METADATA},
};
ddk_.SetMetadata(DEVICE_METADATA_GUID_MAP, &guid_map, sizeof(guid_map));
TableRef tab;
ASSERT_OK(PartitionTable::Create(fake_ddk::kFakeParent, &tab, &devices));
ASSERT_OK(tab->Bind());
ASSERT_EQ(devices.size(), 2);
char name[MAX_PARTITION_NAME_LENGTH];
guid_t guid;
// Device 0
PartitionDevice* dev0 = devices[0].get();
ASSERT_NOT_NULL(dev0);
ASSERT_OK(dev0->BlockPartitionGetName(name, sizeof(name)));
ASSERT_EQ(strcmp(name, "Linux filesystem"), 0);
ASSERT_OK(dev0->BlockPartitionGetGuid(GUIDTYPE_TYPE, &guid));
{
uint8_t expected_guid[GPT_GUID_LEN] = GUID_METADATA;
EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN);
}
ASSERT_OK(dev0->BlockPartitionGetGuid(GUIDTYPE_INSTANCE, &guid));
{
uint8_t expected_guid[GPT_GUID_LEN] = GUID_UNIQUE_PART0;
EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN);
}
PartitionDevice* dev1 = devices[1].get();
ASSERT_NOT_NULL(dev1);
ASSERT_OK(dev1->BlockPartitionGetName(name, sizeof(name)));
ASSERT_EQ(kPartition1Name, name);
ASSERT_OK(dev1->BlockPartitionGetGuid(GUIDTYPE_TYPE, &guid));
{
uint8_t expected_guid[GPT_GUID_LEN] = GUID_LINUX_FILESYSTEM;
EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN);
}
ASSERT_OK(dev1->BlockPartitionGetGuid(GUIDTYPE_INSTANCE, &guid));
{
uint8_t expected_guid[GPT_GUID_LEN] = GUID_UNIQUE_PART1;
EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN);
}
dev0->AsyncRemove();
dev1->AsyncRemove();
EXPECT_TRUE(ddk_.Ok());
}
TEST_F(GptDeviceTest, BlockOpsPropagate) {
Init();
fbl::Vector<std::unique_ptr<PartitionDevice>> devices;
const guid_map_t guid_map[] = {
{"Linux filesystem", GUID_METADATA},
};
ddk_.SetMetadata(DEVICE_METADATA_GUID_MAP, &guid_map, sizeof(guid_map));
TableRef tab;
ASSERT_OK(PartitionTable::Create(fake_ddk::kFakeParent, &tab, &devices));
ASSERT_OK(tab->Bind());
ASSERT_EQ(devices.size(), 2);
PartitionDevice* dev0 = devices[0].get();
PartitionDevice* dev1 = devices[1].get();
block_info_t block_info = {};
size_t block_op_size = 0;
dev0->BlockImplQuery(&block_info, &block_op_size);
EXPECT_EQ(block_op_size, sizeof(block_op_t));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(4 * block_info.block_size, 0, &vmo));
block_op_t op = {};
op.rw.command = BLOCK_OP_READ;
op.rw.vmo = vmo.get();
op.rw.length = 4;
op.rw.offset_dev = 1000;
BlockOpResult result;
dev0->BlockImplQueue(&op, BlockOpCompleter, &result);
sync_completion_wait(&result.completion, ZX_TIME_INFINITE);
sync_completion_reset(&result.completion);
EXPECT_EQ(result.op.command, BLOCK_OP_READ);
EXPECT_EQ(result.op.rw.length, 4);
EXPECT_EQ(result.op.rw.offset_dev, 2048 + 1000);
EXPECT_OK(result.status);
op.rw.command = BLOCK_OP_WRITE;
op.rw.vmo = vmo.get();
op.rw.length = 4;
op.rw.offset_dev = 5000;
dev1->BlockImplQueue(&op, BlockOpCompleter, &result);
sync_completion_wait(&result.completion, ZX_TIME_INFINITE);
sync_completion_reset(&result.completion);
EXPECT_EQ(result.op.command, BLOCK_OP_WRITE);
EXPECT_EQ(result.op.rw.length, 4);
EXPECT_EQ(result.op.rw.offset_dev, 22528 + 5000);
EXPECT_OK(result.status);
op.trim.command = BLOCK_OP_TRIM;
op.trim.length = 16;
op.trim.offset_dev = 10000;
dev0->BlockImplQueue(&op, BlockOpCompleter, &result);
sync_completion_wait(&result.completion, ZX_TIME_INFINITE);
sync_completion_reset(&result.completion);
EXPECT_EQ(result.op.command, BLOCK_OP_TRIM);
EXPECT_EQ(result.op.trim.length, 16);
EXPECT_EQ(result.op.trim.offset_dev, 2048 + 10000);
EXPECT_OK(result.status);
op.command = BLOCK_OP_FLUSH;
dev1->BlockImplQueue(&op, BlockOpCompleter, &result);
sync_completion_wait(&result.completion, ZX_TIME_INFINITE);
sync_completion_reset(&result.completion);
EXPECT_EQ(result.op.command, BLOCK_OP_FLUSH);
EXPECT_OK(result.status);
dev0->AsyncRemove();
dev1->AsyncRemove();
EXPECT_TRUE(ddk_.Ok());
}
TEST_F(GptDeviceTest, BlockOpsOutOfBounds) {
Init();
fbl::Vector<std::unique_ptr<PartitionDevice>> devices;
const guid_map_t guid_map[] = {
{"Linux filesystem", GUID_METADATA},
};
ddk_.SetMetadata(DEVICE_METADATA_GUID_MAP, &guid_map, sizeof(guid_map));
TableRef tab;
ASSERT_OK(PartitionTable::Create(fake_ddk::kFakeParent, &tab, &devices));
ASSERT_OK(tab->Bind());
ASSERT_EQ(devices.size(), 2);
PartitionDevice* dev0 = devices[0].get();
PartitionDevice* dev1 = devices[1].get();
block_info_t block_info = {};
size_t block_op_size = 0;
dev0->BlockImplQuery(&block_info, &block_op_size);
EXPECT_EQ(block_op_size, sizeof(block_op_t));
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(4 * block_info.block_size, 0, &vmo));
block_op_t op = {};
op.rw.command = BLOCK_OP_READ;
op.rw.vmo = vmo.get();
op.rw.length = 4;
op.rw.offset_dev = 20481;
BlockOpResult result;
dev0->BlockImplQueue(&op, BlockOpCompleter, &result);
sync_completion_wait(&result.completion, ZX_TIME_INFINITE);
sync_completion_reset(&result.completion);
EXPECT_NOT_OK(result.status);
op.rw.command = BLOCK_OP_WRITE;
op.rw.vmo = vmo.get();
op.rw.length = 4;
op.rw.offset_dev = 20478;
dev0->BlockImplQueue(&op, BlockOpCompleter, &result);
sync_completion_wait(&result.completion, ZX_TIME_INFINITE);
sync_completion_reset(&result.completion);
EXPECT_NOT_OK(result.status);
op.trim.command = BLOCK_OP_TRIM;
op.trim.length = 18434;
op.trim.offset_dev = 0;
dev1->BlockImplQueue(&op, BlockOpCompleter, &result);
sync_completion_wait(&result.completion, ZX_TIME_INFINITE);
sync_completion_reset(&result.completion);
EXPECT_NOT_OK(result.status);
dev0->AsyncRemove();
dev1->AsyncRemove();
EXPECT_TRUE(ddk_.Ok());
}
} // namespace
} // namespace gpt