[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