[virtio] virtio-scsi driver
virtio-scsi is a storage controller that bridges a virtio bus to
a SCSI bus or busses; each bus may have zero to many storage
targets/Logical Units attached. virtio-scsi supports a rich per-target
command set, asynchronous event notification for disk
attach/detach/resize, 'task management' (cancellation/target reset), and
command queueing.
virtio-scsi is the default storage controller in GCE.
Currently this driver is just a skeleton - it probes every possible SCSI
target with a TEST UNIT READY command and allocates a scsi::Disk bridge
between the zircon block protocol and SCSI layer. Subsequent commits
will implement the translation in scsi::Disk, break up ExecuteCommandSync
into a call to queue a request with a callback and a mechanism to
wait for a command, and complete and pull the probe sequence from
scsi.cpp to scsilib.cpp.
Tested:
1) Created GCE instance w:
$ fx set x64 --args=kernel_cmdline_files=\[\"//scripts/gce/kernel-cmdline.txt\"\]
$ fx full-build
$ fx gce create-fuchsia-image && fx gce create-instance
<connected to serial port>
Saw:
[00007.306] 02580.02790> scsi-disk-1-0: added
[00007.316] 02580.02603> block: device 'scsi-disk-1-0': invalid block size: 0
[00007.316] 02580.02603> devhost[00:03.0/virtio-scsi/scsi-disk-1-0] bind driver '/boot/driver/block.so' failed: -2
[00007.318] 01102.01115> devcoord: rpc: bind-driver 'scsi-disk-1-0' status -2
(size zero is expected.)
2) Ran dm dump:
...
[00:03.0] pid=2012 /boot/driver/bus-pci.so
<00:03.0> pid=2921 /boot/driver/bus-pci.proxy.so
[virtio-scsi] pid=2921 /boot/driver/virtio.so
...
3) Attached four scsi disks, rebooted, saw:
[00007.431] 02572.02595> virtual zx_status_t virtio::ScsiDevice::Init(): entry
[00007.458] 02572.02756> scsi-disk-1-0: status=0
[00007.463] 02572.02756> scsi-disk-2-0: status=0
[00007.470] 02572.02756> scsi-disk-3-0: status=0
[00007.476] 02572.02756> scsi-disk-4-0: status=0
...
[00027.252] 01102.01115> devcoord: device 0x45b762509e00 name='00:04.0' disconnected!
[00027.261] 01102.01115> devcoord: device 0x45b762509c00 name='00:03.0' disconnected!
[00027.268] 01102.01115> devcoord: device 0x45b762509b00 name='00:01.3' disconnected!
[00027.270] 01102.01115> devcoord: device 0x45b762509a00 name='00:01.0' disconnected!
[00027.271] 01102.01115> devcoord: device 0x45b762509900 name='00:00.0' disconnected!
ZX-2314
Change-Id: Ic361da103d63bf3e0dad09d479340d6ca4d034e2
diff --git a/system/dev/bus/virtio/backends/fake.h b/system/dev/bus/virtio/backends/fake.h
index 3d0590f..5f9412c 100644
--- a/system/dev/bus/virtio/backends/fake.h
+++ b/system/dev/bus/virtio/backends/fake.h
@@ -42,28 +42,40 @@
state_ = State::DEVICE_RESET;
}
void ReadDeviceConfig(uint16_t offset, uint8_t* value) override {
- EXPECT_GT(registers8_.count(offset), 0);
- *value = registers8_[offset];
+ char message[80] = {};
+ snprintf(message, sizeof(message), "offset-%xh/8", offset);
+ auto shifted_offset = static_cast<uint16_t>(offset + kISRStatus + 1);
+ EXPECT_GT(registers8_.count(shifted_offset), 0, message);
+ *value = registers8_[shifted_offset];
}
void ReadDeviceConfig(uint16_t offset, uint16_t* value) override {
- EXPECT_GT(registers16_.count(offset), 0);
- *value = registers16_[offset];
+ char message[80] = {};
+ snprintf(message, sizeof(message), "offset-%xh/16", offset);
+ auto shifted_offset = static_cast<uint16_t>(offset + kISRStatus + 1);
+ EXPECT_GT(registers16_.count(shifted_offset), 0, message);
+ *value = registers16_[shifted_offset];
}
void ReadDeviceConfig(uint16_t offset, uint32_t* value) override {
- EXPECT_GT(registers32_.count(offset), 0);
- *value = registers32_[offset];
+ char message[80] = {};
+ snprintf(message, sizeof(message), "offset-%xh/32", offset);
+ auto shifted_offset = static_cast<uint16_t>(offset + kISRStatus + 1);
+ EXPECT_GT(registers32_.count(shifted_offset), 0, message);
+ *value = registers32_[shifted_offset];
}
void ReadDeviceConfig(uint16_t offset, uint64_t* value) override {
EXPECT_TRUE(0); // Not Implemented.
}
void WriteDeviceConfig(uint16_t offset, uint8_t value) override {
- registers8_[offset] = value;
+ auto shifted_offset = static_cast<uint16_t>(offset + kISRStatus + 1);
+ registers8_[shifted_offset] = value;
}
void WriteDeviceConfig(uint16_t offset, uint16_t value) override {
- registers16_[offset] = value;
+ auto shifted_offset = static_cast<uint16_t>(offset + kISRStatus + 1);
+ registers16_[shifted_offset] = value;
}
void WriteDeviceConfig(uint16_t offset, uint32_t value) override {
- registers32_[offset] = value;
+ auto shifted_offset = static_cast<uint16_t>(offset + kISRStatus + 1);
+ registers32_[shifted_offset] = value;
}
void WriteDeviceConfig(uint16_t offset, uint64_t value) override {
EXPECT_TRUE(0); // Not Implemented.
@@ -79,26 +91,72 @@
EXPECT_GT(queue_sizes_.count(ring_index), 0);
kicked_queues_.insert(ring_index);
}
- uint32_t IsrStatus() override { return 0; }
+ uint32_t IsrStatus() override { return registers8_.find(kISRStatus)->second; }
zx_status_t InterruptValid() override { return ZX_OK; }
zx_status_t WaitForInterrupt() override { return ZX_OK; }
protected:
- FakeBackend(std::initializer_list<std::pair<const uint16_t, uint8_t>> registers8,
- std::initializer_list<std::pair<const uint16_t, uint16_t>> registers16,
- std::initializer_list<std::pair<const uint16_t, uint32_t>> registers32,
- std::initializer_list<std::pair<const uint16_t, uint16_t>> queue_sizes):
- registers8_(registers8), registers16_(registers16), registers32_(registers32),
- queue_sizes_(queue_sizes) {}
+ // virtio header register offsets.
+ static constexpr uint16_t kDeviceFeatures = 0;
+ static constexpr uint16_t kGuestFeatures = 4;
+ static constexpr uint16_t kQueueAddress = 8;
+ static constexpr uint16_t kQueueSize = 12;
+ static constexpr uint16_t kQueueSelect = 14;
+ static constexpr uint16_t kQueueNotify = 16;
+ static constexpr uint16_t kDeviceStatus = 18;
+ static constexpr uint16_t kISRStatus = 19;
- // Returns true if a queue has been kicked (notified) and clears the notified bit.
- bool QueueKicked(uint16_t queue_index) {
- bool is_queue_kicked = (kicked_queues_.count(queue_index));
- if (is_queue_kicked) {
- kicked_queues_.erase(queue_index);
- }
- return is_queue_kicked;
- }
+ explicit FakeBackend(std::initializer_list<std::pair<const uint16_t, uint16_t>> queue_sizes):
+ queue_sizes_(queue_sizes) {
+ // Bind standard virtio header registers into register maps.
+ registers32_.insert({kDeviceFeatures, 0});
+ registers32_.insert({kGuestFeatures, 0});
+ registers32_.insert({kQueueAddress, 0});
+ registers16_.insert({kQueueSize, 0});
+ registers16_.insert({kQueueSelect, 0});
+ registers16_.insert({kQueueNotify, 0});
+ registers8_.insert({kDeviceStatus, 0});
+ registers8_.insert({kISRStatus, 0});
+ }
+
+ // Returns true if a queue has been kicked (notified) and clears the notified bit.
+ bool QueueKicked(uint16_t queue_index) {
+ bool is_queue_kicked = (kicked_queues_.count(queue_index));
+ if (is_queue_kicked) {
+ kicked_queues_.erase(queue_index);
+ }
+ return is_queue_kicked;
+ }
+
+ template<typename T> void AddClassRegister(uint16_t offset, T value) {
+ if constexpr(sizeof(T) == 1) {
+ registers8_.insert({kISRStatus + 1 + offset, value});
+ } else if constexpr(sizeof(T) == 2) {
+ registers16_.insert({kISRStatus + 1 + offset, value});
+ } else if constexpr(sizeof(T) == 4) {
+ registers32_.insert({kISRStatus + 1 + offset, value});
+ }
+ }
+
+ template<typename T> void SetRegister(uint16_t offset, T value) {
+ if constexpr(sizeof(T) == 1) {
+ registers8_[offset] = value;
+ } else if constexpr(sizeof(T) == 2) {
+ registers16_[offset] = value;
+ } else if constexpr(sizeof(T) == 4) {
+ registers32_[offset] = value;
+ }
+ }
+
+ template<typename T> void ReadRegister(uint16_t offset, T* output) {
+ if constexpr(sizeof(T) == 1) {
+ *output = registers8_.find(offset)->second;
+ } else if constexpr(sizeof(T) == 2) {
+ *output = registers16_.find(offset)->second;
+ } else if constexpr(sizeof(T) == 4) {
+ *output = registers32_.find(offset)->second;
+ }
+ }
private:
enum class State {
diff --git a/system/dev/bus/virtio/rules.mk b/system/dev/bus/virtio/rules.mk
index c029cc2..8cee6c6 100644
--- a/system/dev/bus/virtio/rules.mk
+++ b/system/dev/bus/virtio/rules.mk
@@ -19,6 +19,8 @@
$(LOCAL_DIR)/ring.cpp \
$(LOCAL_DIR)/rng.cpp \
$(LOCAL_DIR)/socket.cpp \
+ $(LOCAL_DIR)/scsi.cpp \
+ $(LOCAL_DIR)/scsilib.cpp \
$(LOCAL_DIR)/virtio_driver.cpp \
$(LOCAL_DIR)/backends/pci.cpp \
$(LOCAL_DIR)/backends/pci_legacy.cpp \
@@ -53,3 +55,47 @@
MODULE_LIBS := system/ulib/driver system/ulib/zircon system/ulib/c
include make/module.mk
+
+# Unit tests
+MODULE := $(LOCAL_DIR).test
+MODULE_TYPE := usertest
+MODULE_SRCS := \
+ $(LOCAL_DIR)/device.cpp \
+ $(LOCAL_DIR)/scsi.cpp \
+ $(LOCAL_DIR)/scsilib.cpp \
+ $(LOCAL_DIR)/ring.cpp \
+ $(LOCAL_DIR)/scsi_test.cpp \
+ $(LOCAL_DIR)/test_main.cpp \
+
+MODULE_STATIC_LIBS := \
+ system/dev/lib/fake_ddk \
+ system/ulib/async \
+ system/ulib/async.cpp \
+ system/ulib/async-loop \
+ system/ulib/async-loop.cpp \
+ system/ulib/ddk \
+ system/ulib/ddktl \
+ system/ulib/fbl \
+ system/ulib/fidl \
+ system/ulib/hid \
+ system/ulib/hwreg \
+ system/ulib/pretty \
+ system/ulib/sync \
+ system/ulib/unittest \
+ system/ulib/virtio \
+ system/ulib/zx \
+ system/ulib/zxcpp \
+
+MODULE_LIBS := \
+ system/ulib/driver \
+ system/ulib/zircon \
+ system/ulib/c
+
+MODULE_BANJO_LIBS := \
+ system/banjo/ddk-protocol-block \
+ system/banjo/ddk-protocol-pci \
+
+MODULE_COMPILEFLAGS := \
+ -I$(LOCAL_DIR)\
+
+include make/module.mk
diff --git a/system/dev/bus/virtio/scsi.cpp b/system/dev/bus/virtio/scsi.cpp
new file mode 100644
index 0000000..afe1b4c
--- /dev/null
+++ b/system/dev/bus/virtio/scsi.cpp
@@ -0,0 +1,220 @@
+// 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 "scsi.h"
+
+#include <ddk/debug.h>
+#include <fbl/algorithm.h>
+#include <fbl/auto_call.h>
+#include <fbl/auto_lock.h>
+#include <inttypes.h>
+#include <pretty/hexdump.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <virtio/scsi.h>
+#include <zircon/compiler.h>
+
+#include <utility>
+
+#include "scsilib.h"
+#include "trace.h"
+
+#define LOCAL_TRACE 0
+
+namespace virtio {
+
+// Fill in req->lun with a single-level LUN structure representing target:lun.
+static void FillLUNStructure(struct virtio_scsi_req_cmd* req, uint8_t target, uint16_t lun) {
+ req->lun[0] = 1;
+ req->lun[1] = target;
+ memcpy(&req->lun[2], &lun, sizeof(lun));
+}
+
+zx_status_t ScsiDevice::ExecuteCommandSync(uint8_t target, uint16_t lun, uint8_t* cdb,
+ size_t cdb_length) {
+ uint8_t* const request_buffers_addr =
+ reinterpret_cast<uint8_t*>(io_buffer_virt(&request_buffers_));
+ auto* const req = reinterpret_cast<struct virtio_scsi_req_cmd*>(request_buffers_addr);
+ auto* const resp = reinterpret_cast<struct virtio_scsi_resp_cmd*>(
+ request_buffers_addr + sizeof(struct virtio_scsi_req_cmd));
+
+ memset(req, 0, sizeof(*req));
+ memcpy(&req->cdb, cdb, cdb_length);
+ FillLUNStructure(req, /*target=*/target, /*lun=*/lun);
+
+ // virtio-scsi requests have a 'request' region, a data-out region, a
+ // 'response' region, and a data-in region. Allocate and fill them
+ // and then execute the request.
+ //
+ // TODO: Currently only allocates two regions, request/response.
+ // Add more so we can support most SCSI commands.
+ uint16_t id = 0;
+ auto request_desc = request_queue_.AllocDescChain(/*count=*/2, &id);
+ request_desc->addr = io_buffer_phys(&request_buffers_);
+ request_desc->len = sizeof(*req);
+ request_desc->flags = VRING_DESC_F_NEXT;
+
+ auto response_desc = request_queue_.DescFromIndex(request_desc->next);
+ response_desc->addr = io_buffer_phys(&request_buffers_) + sizeof(*req);
+ response_desc->len = sizeof(*resp);
+ response_desc->flags = VRING_DESC_F_WRITE;
+
+ request_queue_.SubmitChain(id);
+ request_queue_.Kick();
+
+ // Wait for request to complete.
+ sync_completion_t sync;
+ // annotalysis is unable to determine that ScsiDevice::lock_ is held when the IrqRingUpdate
+ // lambda is invoked.
+ request_queue_.IrqRingUpdate([this, &sync](vring_used_elem* elem) TA_NO_THREAD_SAFETY_ANALYSIS {
+ auto index = static_cast<uint16_t>(elem->id);
+
+ // Synchronously reclaim the entire descriptor chain.
+ for (;;) {
+ vring_desc const* desc = request_queue_.DescFromIndex(index);
+ const bool has_next = desc->flags & VRING_DESC_F_NEXT;
+ const uint16_t next = desc->next;
+
+ this->request_queue_.FreeDesc(index);
+ if (!has_next) {
+ break;
+ }
+ index = next;
+ }
+ sync_completion_signal(&sync);
+ });
+ sync_completion_wait(&sync, ZX_TIME_INFINITE);
+
+ // If there was either a transport or SCSI level error, return a failure.
+ if (resp->response || resp->status) {
+ return ZX_ERR_INTERNAL;
+ }
+
+ return ZX_OK;
+}
+
+zx_status_t ScsiDevice::WorkerThread() {
+ fbl::AutoLock lock(&lock_);
+
+ // Execute TEST UNIT READY on every possible target to find potential disks.
+ // TODO(ZX-2314): Move probe sequence to ScsiLib -- have it call down into LLDs to execute
+ // commands.
+ for (auto channel = 0u; channel < config_.max_channel; channel++) {
+ for (uint8_t target = 0u; target < config_.max_target; target++) {
+ for (uint16_t lun = 0u; lun < config_.max_lun; lun++) {
+ scsi::TestUnitReadyCDB cdb = {};
+ cdb.opcode = scsi::Opcode::TEST_UNIT_READY;
+
+ auto status = ExecuteCommandSync(
+ /*target=*/target,
+ /*lun=*/lun, reinterpret_cast<uint8_t*>(&cdb), sizeof(cdb));
+ if (status == ZX_OK) {
+ scsi::Disk::Create(device_, /*target=*/target, /*lun=*/lun);
+ }
+ }
+ }
+ }
+
+ return ZX_OK;
+}
+
+zx_status_t ScsiDevice::Init() {
+ LTRACE_ENTRY;
+
+ Device::DeviceReset();
+ Device::ReadDeviceConfig<uint32_t>(offsetof(virtio_scsi_config, num_queues),
+ &config_.num_queues);
+ Device::ReadDeviceConfig<uint32_t>(offsetof(virtio_scsi_config, seg_max),
+ &config_.seg_max);
+ Device::ReadDeviceConfig<uint32_t>(offsetof(virtio_scsi_config, max_sectors),
+ &config_.max_sectors);
+ Device::ReadDeviceConfig<uint32_t>(offsetof(virtio_scsi_config, cmd_per_lun),
+ &config_.cmd_per_lun);
+ Device::ReadDeviceConfig<uint32_t>(offsetof(virtio_scsi_config, event_info_size),
+ &config_.event_info_size);
+ Device::ReadDeviceConfig<uint32_t>(offsetof(virtio_scsi_config, sense_size),
+ &config_.sense_size);
+ Device::ReadDeviceConfig<uint32_t>(offsetof(virtio_scsi_config, cdb_size),
+ &config_.cdb_size);
+ Device::ReadDeviceConfig<uint16_t>(offsetof(virtio_scsi_config, max_channel),
+ &config_.max_channel);
+ Device::ReadDeviceConfig<uint16_t>(offsetof(virtio_scsi_config, max_target),
+ &config_.max_target);
+ Device::ReadDeviceConfig<uint32_t>(offsetof(virtio_scsi_config, max_lun),
+ &config_.max_lun);
+
+ Device::DriverStatusAck();
+
+ {
+ fbl::AutoLock lock(&lock_);
+ auto err = control_ring_.Init(/*index=*/Queue::CONTROL);
+ if (err) {
+ zxlogf(ERROR, "failed to allocate control queue\n");
+ return err;
+ }
+
+ err = request_queue_.Init(/*index=*/Queue::REQUEST);
+ if (err) {
+ zxlogf(ERROR, "failed to allocate request queue\n");
+ return err;
+ }
+
+ // Allocate one virtio_scsi_req_cmd / virtio_scsi_resp_cmd per request
+ // queue entry.
+ const size_t request_buffers_size =
+ Device::GetRingSize(Queue::REQUEST) *
+ (sizeof(struct virtio_scsi_req_cmd) + sizeof(struct virtio_scsi_resp_cmd));
+ auto status =
+ io_buffer_init(&request_buffers_, bti().get(),
+ /*size=*/request_buffers_size, IO_BUFFER_RW | IO_BUFFER_CONTIG);
+ if (status) {
+ zxlogf(ERROR, "failed to allocate queue working memory\n");
+ return status;
+ }
+ }
+
+ Device::StartIrqThread();
+ Device::DriverStatusOk();
+
+ device_add_args_t args{};
+ args.version = DEVICE_ADD_ARGS_VERSION;
+ args.name = "virtio-scsi";
+ args.ops = &device_ops_;
+ args.ctx = this;
+
+ // Synchronize against Unbind()/Release() before the worker thread is running.
+ fbl::AutoLock lock(&lock_);
+ auto status = device_add(Device::bus_device_, &args, &device_);
+ if (status != ZX_OK) {
+ return status;
+ }
+
+ auto td = [](void* ctx) {
+ ScsiDevice* const device = static_cast<ScsiDevice*>(ctx);
+ return device->WorkerThread();
+ };
+ int ret = thrd_create_with_name(&worker_thread_, td, this, "virtio-scsi-worker");
+ if (ret != thrd_success) {
+ return ZX_ERR_INTERNAL;
+ }
+
+ return status;
+}
+
+void ScsiDevice::Unbind() {
+ Device::Unbind();
+}
+
+void ScsiDevice::Release() {
+ {
+ fbl::AutoLock lock(&lock_);
+ worker_thread_should_exit_ = true;
+ }
+ thrd_join(worker_thread_, nullptr);
+ Device::Release();
+}
+
+} // namespace virtio
diff --git a/system/dev/bus/virtio/scsi.h b/system/dev/bus/virtio/scsi.h
new file mode 100644
index 0000000..414abd0
--- /dev/null
+++ b/system/dev/bus/virtio/scsi.h
@@ -0,0 +1,65 @@
+// 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.
+
+#pragma once
+
+#include "device.h"
+#include "ring.h"
+
+#include <atomic>
+#include <stdlib.h>
+
+#include "backends/backend.h"
+#include <lib/sync/completion.h>
+#include <virtio/scsi.h>
+#include <zircon/compiler.h>
+#include <zircon/thread_annotations.h>
+
+namespace virtio {
+
+class ScsiDevice : public Device {
+public:
+ enum Queue {
+ CONTROL = 0,
+ EVENT = 1,
+ REQUEST = 2,
+ };
+
+ ScsiDevice(zx_device_t* device, zx::bti bti, fbl::unique_ptr<Backend> backend)
+ : Device(device, std::move(bti), std::move(backend)) {}
+
+ // virtio::Device overrides
+ zx_status_t Init() override;
+ void Unbind() override;
+ void Release() override;
+ // Invoked for most device interrupts.
+ void IrqRingUpdate() override {}
+ // Invoked on config change interrupts.
+ void IrqConfigChange() override {}
+
+ const char* tag() const override { return "virtio-scsi"; }
+
+private:
+ zx_status_t ExecuteCommandSync(uint8_t target, uint16_t lun, uint8_t* cdb, size_t cdb_length)
+ TA_REQ(lock_);
+
+ zx_status_t WorkerThread();
+
+ // Latched copy of virtio-scsi device configuration.
+ struct virtio_scsi_config config_ TA_GUARDED(lock_) = {};
+
+ // DMA Memory for virtio-scsi requests/responses, events, task management functions.
+ io_buffer_t request_buffers_ TA_GUARDED(lock_) = {};
+
+ Ring control_ring_ TA_GUARDED(lock_) = {this};
+ Ring request_queue_ TA_GUARDED(lock_) = {this};
+
+ thrd_t worker_thread_;
+ bool worker_thread_should_exit_ TA_GUARDED(lock_) = {};
+
+ // Synchronizes virtio rings and worker thread control.
+ fbl::Mutex lock_;
+};
+
+} // namespace virtio
diff --git a/system/dev/bus/virtio/scsi_test.cpp b/system/dev/bus/virtio/scsi_test.cpp
new file mode 100644
index 0000000..9709d05
--- /dev/null
+++ b/system/dev/bus/virtio/scsi_test.cpp
@@ -0,0 +1,49 @@
+// 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 <lib/fake_ddk/fake_ddk.h>
+#include <unittest/unittest.h>
+
+#include "backends/fake.h"
+#include "scsi.h"
+
+using Queue = virtio::ScsiDevice::Queue;
+
+namespace {
+
+// Fake virtio 'backend' for a virtio-scsi device.
+class FakeBackendForScsi : public virtio::FakeBackend {
+ public:
+ FakeBackendForScsi() : virtio::FakeBackend(
+ /*queue_sizes=*/{{Queue::CONTROL, 128}, {Queue::REQUEST, 128}, {Queue::EVENT, 128}}) {
+ // TODO(venkateshs): Sane defaults for these registers.
+ AddClassRegister(offsetof(virtio_scsi_config, num_queues), 1);
+ AddClassRegister(offsetof(virtio_scsi_config, seg_max), 1);
+ AddClassRegister(offsetof(virtio_scsi_config, max_sectors), 1);
+ AddClassRegister(offsetof(virtio_scsi_config, cmd_per_lun), 1);
+ AddClassRegister(offsetof(virtio_scsi_config, event_info_size), 1);
+ AddClassRegister(offsetof(virtio_scsi_config, sense_size), 1);
+ AddClassRegister(offsetof(virtio_scsi_config, cdb_size), 1);
+ AddClassRegister(offsetof(virtio_scsi_config, max_channel), static_cast<uint16_t>(1));
+ AddClassRegister(offsetof(virtio_scsi_config, max_target), static_cast<uint16_t>(1));
+ AddClassRegister(offsetof(virtio_scsi_config, max_lun), 1);
+ }
+};
+
+bool InitTest() {
+ BEGIN_TEST;
+ fbl::unique_ptr<virtio::Backend> backend = fbl::make_unique<FakeBackendForScsi>();
+ zx::bti bti(ZX_HANDLE_INVALID);
+
+ virtio::ScsiDevice scsi(/*parent=*/nullptr, std::move(bti), std::move(backend));
+ auto status = scsi.Init();
+ EXPECT_NE(status, ZX_OK);
+ END_TEST;
+}
+
+} // anonymous namespace
+
+BEGIN_TEST_CASE(ScsiDriverTests)
+RUN_TEST_SMALL(InitTest)
+END_TEST_CASE(ScsiDriverTests)
diff --git a/system/dev/bus/virtio/scsilib.cpp b/system/dev/bus/virtio/scsilib.cpp
new file mode 100644
index 0000000..033e379
--- /dev/null
+++ b/system/dev/bus/virtio/scsilib.cpp
@@ -0,0 +1,34 @@
+// Copyriht 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 "scsilib.h"
+
+#include <ddk/protocol/block.h>
+#include <fbl/alloc_checker.h>
+
+namespace scsi {
+
+zx_status_t Disk::Create(zx_device_t* parent, uint8_t target, uint16_t lun) {
+ fbl::AllocChecker ac;
+ auto* const disk = new (&ac) scsi::Disk(parent, /*target=*/target, /*lun=*/lun);
+ if (!ac.check()) {
+ return ZX_ERR_NO_MEMORY;
+ }
+ auto status = disk->Bind();
+ if (status != ZX_OK) {
+ delete disk;
+ }
+ return status;
+}
+
+zx_status_t Disk::Bind() {
+ return DdkAdd(tag_);
+}
+
+Disk::Disk(zx_device_t* parent, uint8_t target, uint16_t lun)
+ : DeviceType(parent), target_(target), lun_(lun) {
+ snprintf(tag_, sizeof(tag_), "scsi-disk-%d-%d", target_, lun_);
+}
+
+} // namespace scsi
diff --git a/system/dev/bus/virtio/scsilib.h b/system/dev/bus/virtio/scsilib.h
new file mode 100644
index 0000000..ea308d0
--- /dev/null
+++ b/system/dev/bus/virtio/scsilib.h
@@ -0,0 +1,76 @@
+// 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.
+
+#pragma once
+
+#include <ddk/device.h>
+#include <ddk/driver.h>
+#include <ddk/protocol/block.h>
+#include <ddktl/device.h>
+#include <ddktl/protocol/block.h>
+#include <stdint.h>
+
+namespace scsi {
+
+enum class Opcode : uint8_t {
+ TEST_UNIT_READY = 0x00,
+ INQUIRY = 0x12,
+ MODE_SENSE_6 = 0x1A,
+ READ_16 = 0x88,
+ WRITE_16 = 0x8A,
+};
+
+// SCSI command structures (CDBs)
+
+struct TestUnitReadyCDB {
+ Opcode opcode;
+ uint8_t reserved[4];
+ uint8_t control;
+} __PACKED;
+
+static_assert(sizeof(TestUnitReadyCDB) == 6, "TestUnitReady CDB must be 6 bytes");
+
+class Disk;
+using DeviceType = ddk::Device<Disk, ddk::GetSizable, ddk::Unbindable>;
+
+// |Disk| represents a single SCSI direct access block device.
+// |Disk| bridges between the Zircon block protocol and SCSI commands/responses.
+class Disk : public DeviceType, public ddk::BlockImplProtocol<Disk, ddk::base_protocol> {
+ public:
+ // Public so that we can use make_unique.
+ // Clients should use Disk::Create().
+ Disk(zx_device_t* parent, uint8_t target, uint16_t lun);
+
+ // Create a Disk at a specific target/lun.
+ static zx_status_t Create(zx_device_t* parent, uint8_t target, uint16_t lun);
+
+ const char* tag() const { return tag_; }
+
+ // DeviceType functions.
+ void DdkUnbind() { DdkRemove(); }
+ void DdkRelease() { delete this; }
+
+ // ddk::GetSizable functions.
+ zx_off_t DdkGetSize() { return 0; }
+
+ // ddk::BlockImplProtocol functions.
+ // TODO(ZX-2314): Implement these two functions.
+ void BlockImplQuery(block_info_t* info_out, size_t* block_op_size_out) {}
+ void BlockImplQueue(block_op_t* operation, block_impl_queue_callback completion_cb,
+ void* cookie) {
+ completion_cb(cookie, ZX_ERR_NOT_SUPPORTED, operation);
+ }
+
+ Disk(const Disk&) = delete;
+ Disk& operator=(const Disk&) = delete;
+
+ private:
+ zx_status_t Bind();
+
+ char tag_[24];
+ const uint8_t target_;
+ const uint16_t lun_;
+};
+
+} // namespace scsi
diff --git a/system/dev/bus/virtio/test_main.cpp b/system/dev/bus/virtio/test_main.cpp
new file mode 100644
index 0000000..faff464
--- /dev/null
+++ b/system/dev/bus/virtio/test_main.cpp
@@ -0,0 +1,9 @@
+// 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 <unittest/unittest.h>
+
+int main(int argc, char** argv) {
+ return unittest_run_all_tests(argc, argv) ? 0 : -1;
+}
diff --git a/system/dev/bus/virtio/virtio_driver.cpp b/system/dev/bus/virtio/virtio_driver.cpp
index f4b6bfd..f9da9a8 100644
--- a/system/dev/bus/virtio/virtio_driver.cpp
+++ b/system/dev/bus/virtio/virtio_driver.cpp
@@ -29,6 +29,7 @@
#include "gpu.h"
#include "input.h"
#include "rng.h"
+#include "scsi.h"
#include "socket.h"
static zx_status_t virtio_pci_bind(void* ctx, zx_device_t* bus_device) {
@@ -106,6 +107,11 @@
virtio_device.reset(new virtio::SocketDevice(bus_device, std::move(bti),
std::move(backend)));
break;
+ case VIRTIO_DEV_TYPE_SCSI:
+ case VIRTIO_DEV_TYPE_T_SCSI_HOST:
+ virtio_device.reset(new virtio::ScsiDevice(bus_device, std::move(bti),
+ std::move(backend)));
+ break;
default:
return ZX_ERR_NOT_SUPPORTED;
}
@@ -128,17 +134,19 @@
.release = nullptr,
};
-ZIRCON_DRIVER_BEGIN(virtio, virtio_driver_ops, "zircon", "0.1", 14)
+ZIRCON_DRIVER_BEGIN(virtio, virtio_driver_ops, "zircon", "0.1", 16)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI),
BI_ABORT_IF(NE, BIND_PCI_VID, VIRTIO_PCI_VENDOR_ID),
BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_BLOCK),
BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_CONSOLE),
BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_ENTROPY),
BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_NETWORK),
+ BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_SCSI),
BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_T_BLOCK),
BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_T_CONSOLE),
BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_T_ENTROPY),
BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_T_NETWORK),
+ BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_T_SCSI_HOST),
BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_GPU),
BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_INPUT),
BI_MATCH_IF(EQ, BIND_PCI_DID, VIRTIO_DEV_TYPE_SOCKET),
diff --git a/system/ulib/virtio/include/virtio/scsi.h b/system/ulib/virtio/include/virtio/scsi.h
new file mode 100644
index 0000000..3b7b403
--- /dev/null
+++ b/system/ulib/virtio/include/virtio/scsi.h
@@ -0,0 +1,77 @@
+// Copyright 2018 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.
+
+// virtio-scsi device ABI
+// Reference: https://ozlabs.org/~rusty/virtio-spec/virtio-0.9.5.pdf,
+// Appendix I
+
+#pragma once
+
+#include <assert.h>
+#include <stdint.h>
+#include <zircon/compiler.h>
+
+__BEGIN_CDECLS
+
+struct virtio_scsi_config {
+ // Number of request (SCSI Command) queues
+ uint32_t num_queues;
+ uint32_t seg_max;
+ uint32_t max_sectors;
+ uint32_t cmd_per_lun;
+ uint32_t event_info_size;
+ uint32_t sense_size;
+ uint32_t cdb_size;
+ uint16_t max_channel;
+ uint16_t max_target;
+ uint32_t max_lun;
+} __PACKED;
+
+static_assert(sizeof(struct virtio_scsi_config) == 36,
+ "virtio_scsi_config should be 36 bytes.");
+
+#define VIRTIO_SCSI_CDB_DEFAULT_SIZE 32
+#define VIRTIO_SCSI_SENSE_DEFAULT_SIZE 96
+
+// A virtio-scsi request represents a single SCSI command to a single target.
+// The command command has a 'virtio_scsi_req_cmd' from the driver to the
+// device, an optional data out region (again from the driver to the device),
+// a virtio_scsi_resp_cmd from the device to the driver with Sense information
+// (if any), and an optional data in region.
+//
+// The virtio_scsi_req_cmd and resp_cmd structures must be in a single virtio
+// element unless the F_ANY_LAYOUT feature is negotiated.
+struct virtio_scsi_req_cmd {
+ uint8_t lun[8];
+ // tag must be unique for all commands issued to a LUN
+ uint64_t id;
+ // SIMPLE, ORDERED, HEAD OF QUEUE, or ACA; virtio-scsi only supports SIMPLE
+ uint8_t task_attr;
+ uint8_t prio;
+ uint8_t crn;
+ uint8_t cdb[VIRTIO_SCSI_CDB_DEFAULT_SIZE];
+} __PACKED;
+
+static_assert(sizeof(virtio_scsi_req_cmd) == 51,
+ "virtio_scsi_req_cmd should be 51 bytes");
+
+struct virtio_scsi_resp_cmd {
+ uint32_t sense_len;
+ uint32_t residual;
+ uint16_t status_qualifier;
+ uint8_t status;
+ // Transport-level command response, not SCSI command status.
+ // See: ScsiResponse
+ uint8_t response;
+ uint8_t sense[VIRTIO_SCSI_SENSE_DEFAULT_SIZE];
+} __PACKED;
+
+static_assert(sizeof(virtio_scsi_resp_cmd) == 108,
+ "virtio_scsi_resp_cmd should be 108 bytes");
+
+enum class ScsiResponse : uint8_t {
+ VIRTIO_SCSI_S_OK = 0,
+};
+
+__END_CDECLS