blob: 0698e493af4a9482ea696d3aff4dc56a4e349bee [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 "block.h"
#include <lib/fake-bti/bti.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/sync/completion.h>
#include <lib/virtio/backends/fake.h>
#include <memory>
#include <zxtest/zxtest.h>
namespace {
constexpr uint64_t kCapacity = 1024;
constexpr uint64_t kSizeMax = 4000;
constexpr uint64_t kSegMax = 1024;
constexpr uint64_t kBlkSize = 1024;
// Fake virtio 'backend' for a virtio-scsi device.
class FakeBackendForBlock : public virtio::FakeBackend {
public:
FakeBackendForBlock() : virtio::FakeBackend({{0, 1024}}) {
// Fill out a block config:
virtio_blk_config config;
memset(&config, 0, sizeof(config));
config.capacity = kCapacity;
config.size_max = kSizeMax;
config.seg_max = kSegMax;
config.blk_size = kBlkSize;
for (uint16_t i = 0; i < sizeof(config); ++i) {
AddClassRegister(i, reinterpret_cast<uint8_t*>(&config)[i]);
}
}
};
TEST(BlockTest, InitSuccess) {
std::unique_ptr<virtio::Backend> backend = std::make_unique<FakeBackendForBlock>();
zx::bti bti(ZX_HANDLE_INVALID);
fake_bti_create(bti.reset_and_get_address());
fake_ddk::Bind ddk;
virtio::BlockDevice block(fake_ddk::FakeParent(), std::move(bti), std::move(backend));
ASSERT_EQ(block.Init(), ZX_OK);
block.DdkAsyncRemove();
EXPECT_TRUE(ddk.Ok());
block.DdkRelease();
}
// Provides control primitives for tests that issue IO requests to the device.
class BlockDeviceTest : public zxtest::Test {
public:
~BlockDeviceTest() {}
void InitDevice() {
std::unique_ptr<virtio::Backend> backend = std::make_unique<FakeBackendForBlock>();
zx::bti bti(ZX_HANDLE_INVALID);
fake_bti_create(bti.reset_and_get_address());
ddk_ = std::make_unique<fake_ddk::Bind>();
device_ = std::make_unique<virtio::BlockDevice>(fake_ddk::FakeParent(), std::move(bti),
std::move(backend));
ASSERT_EQ(device_->Init(), ZX_OK);
device_->BlockImplQuery(&info_, &operation_size_);
}
void RemoveDevice() {
device_->DdkAsyncRemove();
EXPECT_TRUE(ddk_->Ok());
device_->DdkRelease();
}
static void CompletionCb(void* cookie, zx_status_t status, block_op_t* op) {
BlockDeviceTest* operation = reinterpret_cast<BlockDeviceTest*>(cookie);
operation->operation_status_ = status;
sync_completion_signal(&operation->event_);
}
bool Wait() {
zx_status_t status = sync_completion_wait(&event_, ZX_SEC(5));
sync_completion_reset(&event_);
return status == ZX_OK;
}
zx_status_t OperationStatus() { return operation_status_; }
protected:
std::unique_ptr<virtio::BlockDevice> device_;
block_info_t info_;
size_t operation_size_;
private:
sync_completion_t event_;
std::unique_ptr<fake_ddk::Bind> ddk_;
zx_status_t operation_status_;
};
// Tests trivial attempts to queue one operation.
TEST_F(BlockDeviceTest, QueueOne) {
InitDevice();
virtio::block_txn_t txn;
memset(&txn, 0, sizeof(txn));
txn.op.rw.command = BLOCK_OP_READ;
txn.op.rw.length = 0;
// TODO(fxbug.dev/43065): This should not return ZX_OK when length == 0.
device_->BlockImplQueue(reinterpret_cast<block_op_t*>(&txn), &BlockDeviceTest::CompletionCb,
this);
ASSERT_TRUE(Wait());
ASSERT_EQ(ZX_OK, OperationStatus());
txn.op.rw.length = kCapacity * 10;
device_->BlockImplQueue(reinterpret_cast<block_op_t*>(&txn), &BlockDeviceTest::CompletionCb,
this);
ASSERT_TRUE(Wait());
ASSERT_EQ(ZX_ERR_OUT_OF_RANGE, OperationStatus());
RemoveDevice();
}
TEST_F(BlockDeviceTest, CheckQuery) {
InitDevice();
ASSERT_EQ(info_.block_size, kBlkSize);
ASSERT_EQ(info_.block_count, kCapacity);
ASSERT_GE(info_.max_transfer_size, PAGE_SIZE);
ASSERT_GT(operation_size_, sizeof(block_op_t));
RemoveDevice();
}
} // anonymous namespace