[virtio] Convert virtio block driver to ddktl
Part of a larger set of CLs to break up the virtio drivers
and use the ddktl library to clean up the driver.
This is step 2 of converting the block driver to use ddktl.
Test: A new test is created for this driver. The driver is also
tested by the virtualization package.
See //src/virtualization/docs/README.md for testing instructions.
Change-Id: I539f185c39dff11c2ecd86722251b7eef652e735
diff --git a/zircon/system/dev/bus/virtio/BUILD.gn b/zircon/system/dev/bus/virtio/BUILD.gn
index 4818f32..c11c611 100644
--- a/zircon/system/dev/bus/virtio/BUILD.gn
+++ b/zircon/system/dev/bus/virtio/BUILD.gn
@@ -3,11 +3,18 @@
# found in the LICENSE file.
zx_driver("virtio_block") {
- sources = [
- "block.cc",
- "block_driver.cc",
- ]
+ sources = [ "block_driver.cc" ]
deps = [
+ ":block_lib",
+ ":common",
+ "$zx/system/banjo/ddk.protocol.block",
+ "$zx/system/ulib/ddktl",
+ ]
+}
+
+source_set("block_lib") {
+ sources = [ "block.cc" ]
+ public_deps = [
":common",
"$zx/system/banjo/ddk.protocol.block",
"$zx/system/ulib/ddktl",
@@ -164,10 +171,12 @@
zx_test("virtio-test") {
sources = [
+ "block_test.cc",
"gpu_test.cc",
"scsi_test.cc",
]
deps = [
+ ":block_lib",
":common",
":gpu_lib",
":scsi_lib",
diff --git a/zircon/system/dev/bus/virtio/block.cc b/zircon/system/dev/bus/virtio/block.cc
index 0ca452d..481d83d 100644
--- a/zircon/system/dev/bus/virtio/block.cc
+++ b/zircon/system/dev/bus/virtio/block.cc
@@ -42,56 +42,40 @@
// DDK level ops
-// Optional: return the size (in bytes) of the readable/writable space of the device. Will default
-// to 0 (non-seekable) if this is unimplemented.
-zx_off_t BlockDevice::virtio_block_get_size(void* ctx) {
- LTRACEF("ctx %p\n", ctx);
-
- BlockDevice* bd = static_cast<BlockDevice*>(ctx);
-
- return bd->GetSize();
-}
-
-void BlockDevice::GetInfo(block_info_t* info) {
+void BlockDevice::BlockImplQuery(block_info_t* info, size_t* bopsz) {
memset(info, 0, sizeof(*info));
info->block_size = GetBlockSize();
- info->block_count = GetSize() / GetBlockSize();
+ info->block_count = DdkGetSize() / GetBlockSize();
info->max_transfer_size = (uint32_t)(PAGE_SIZE * (ring_size - 2));
// Limit max transfer to our worst case scatter list size.
if (info->max_transfer_size > MAX_MAX_XFER) {
info->max_transfer_size = MAX_MAX_XFER;
}
-}
-
-void BlockDevice::virtio_block_query(void* ctx, block_info_t* info, size_t* bopsz) {
- BlockDevice* bd = static_cast<BlockDevice*>(ctx);
- bd->GetInfo(info);
*bopsz = sizeof(block_txn_t);
}
-void BlockDevice::virtio_block_queue(void* ctx, block_op_t* bop,
- block_impl_queue_callback completion_cb, void* cookie) {
- BlockDevice* bd = static_cast<BlockDevice*>(ctx);
+void BlockDevice::BlockImplQueue(block_op_t* bop, block_impl_queue_callback completion_cb,
+ void* cookie) {
block_txn_t* txn = static_cast<block_txn_t*>((void*)bop);
txn->pmt = ZX_HANDLE_INVALID;
txn->completion_cb = completion_cb;
txn->cookie = cookie;
- bd->SignalWorker(txn);
+ SignalWorker(txn);
}
-void BlockDevice::virtio_block_unbind(void* ctx) {
- BlockDevice* bd = static_cast<BlockDevice*>(ctx);
- bd->Unbind();
-}
-
-void BlockDevice::virtio_block_release(void* ctx) {
- std::unique_ptr<BlockDevice> bd(static_cast<BlockDevice*>(ctx));
- bd->Release();
+zx_status_t BlockDevice::DdkGetProtocol(uint32_t proto_id, void* out) {
+ auto* proto = static_cast<ddk::AnyProtocol*>(out);
+ proto->ctx = this;
+ if (proto_id == ZX_PROTOCOL_BLOCK_IMPL) {
+ proto->ops = &block_impl_protocol_ops_;
+ return ZX_OK;
+ }
+ return ZX_ERR_NOT_SUPPORTED;
}
BlockDevice::BlockDevice(zx_device_t* bus_device, zx::bti bti, std::unique_ptr<Backend> backend)
- : Device(bus_device, std::move(bti), std::move(backend)) {
+ : virtio::Device(bus_device, std::move(bti), std::move(backend)), DeviceType(bus_device) {
sync_completion_reset(&txn_signal_);
sync_completion_reset(&worker_signal_);
@@ -163,23 +147,10 @@
}
// Initialize and publish the zx_device.
- device_ops_.get_size = &virtio_block_get_size;
- device_ops_.unbind = &virtio_block_unbind;
- device_ops_.release = &virtio_block_release;
-
- block_ops_.query = &virtio_block_query;
- block_ops_.queue = &virtio_block_queue;
-
- device_add_args_t args = {};
- args.version = DEVICE_ADD_ARGS_VERSION;
- args.name = "virtio-block";
- args.ctx = this;
- args.ops = &device_ops_;
- args.proto_id = ZX_PROTOCOL_BLOCK_IMPL;
- args.proto_ops = &block_ops_;
-
- status = device_add(bus_device_, &args, &device_);
+ status = DdkAdd("virtio-block");
+ device_ = zxdev();
if (status != ZX_OK) {
+ zxlogf(ERROR, "failed to run DdkAdd\n");
device_ = nullptr;
return status;
}
@@ -188,17 +159,17 @@
return ZX_OK;
}
-void BlockDevice::Release() {
+void BlockDevice::DdkRelease() {
thrd_join(worker_thread_, nullptr);
io_buffer_release(&blk_req_buf_);
- Device::Release();
+ virtio::Device::Release();
}
-void BlockDevice::Unbind() {
+void BlockDevice::DdkUnbindDeprecated() {
worker_shutdown_.store(true);
sync_completion_signal(&worker_signal_);
sync_completion_signal(&txn_signal_);
- Device::Unbind();
+ virtio::Device::Unbind();
}
void BlockDevice::IrqRingUpdate() {
diff --git a/zircon/system/dev/bus/virtio/block.h b/zircon/system/dev/bus/virtio/block.h
index 59b7896..525d573 100644
--- a/zircon/system/dev/bus/virtio/block.h
+++ b/zircon/system/dev/bus/virtio/block.h
@@ -13,6 +13,8 @@
#include <memory>
#include <ddk/protocol/block.h>
+#include <ddktl/device.h>
+#include <ddktl/protocol/block.h>
#include <virtio/block.h>
#include "backends/backend.h"
@@ -32,36 +34,37 @@
};
class Ring;
-
-class BlockDevice : public Device {
+class BlockDevice;
+using DeviceType =
+ ddk::Device<BlockDevice, ddk::GetProtocolable, ddk::GetSizable, ddk::UnbindableDeprecated>;
+class BlockDevice : public Device,
+ // Mixins for protocol device:
+ public DeviceType,
+ // Mixin for Block banjo protocol:
+ public ddk::BlockImplProtocol<BlockDevice, ddk::base_protocol> {
public:
BlockDevice(zx_device_t* device, zx::bti bti, std::unique_ptr<Backend> backend);
virtual zx_status_t Init() override;
- virtual void Release() override;
- virtual void Unbind() override;
+
+ // DDKTL device hooks:
+ void DdkRelease();
+ void DdkUnbindDeprecated();
+ zx_off_t DdkGetSize() const { return config_.capacity * config_.blk_size; }
+ zx_status_t DdkGetProtocol(uint32_t proto_id, void* out);
virtual void IrqRingUpdate() override;
virtual void IrqConfigChange() override;
- uint64_t GetSize() const { return config_.capacity * config_.blk_size; }
uint32_t GetBlockSize() const { return config_.blk_size; }
uint64_t GetBlockCount() const { return config_.capacity; }
const char* tag() const override { return "virtio-blk"; }
+ // DDKTL Block protocol banjo functions:
+ void BlockImplQuery(block_info_t* bi, size_t* bopsz);
+ void BlockImplQueue(block_op_t* bop, block_impl_queue_callback completion_cb, void* cookie);
+
private:
- // DDK driver hooks
- static zx_off_t virtio_block_get_size(void* ctx);
-
- static void virtio_block_query(void* ctx, block_info_t* bi, size_t* bopsz);
- static void virtio_block_queue(void* ctx, block_op_t* bop,
- block_impl_queue_callback completion_cb, void* cookie);
-
- static void virtio_block_unbind(void* ctx);
- static void virtio_block_release(void* ctx);
-
- void GetInfo(block_info_t* info);
-
void SignalWorker(block_txn_t* txn);
void WorkerThread();
void FlushPendingTxns();
@@ -117,8 +120,6 @@
list_node worker_txn_list_ = LIST_INITIAL_VALUE(worker_txn_list_);
sync_completion_t worker_signal_;
std::atomic_bool worker_shutdown_ = false;
-
- block_impl_protocol_ops_t block_ops_ = {};
};
} // namespace virtio
diff --git a/zircon/system/dev/bus/virtio/block_test.cc b/zircon/system/dev/bus/virtio/block_test.cc
new file mode 100644
index 0000000..ff61f62
--- /dev/null
+++ b/zircon/system/dev/bus/virtio/block_test.cc
@@ -0,0 +1,132 @@
+// 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 <memory>
+
+#include <zxtest/zxtest.h>
+
+#include "backends/fake.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(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