blob: d25f3400e4cb6786b113066007464383993212bf [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 "../usb-mass-storage.h"
#include "../block.h"
#include <fbl/array.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/ref_ptr.h>
#include <fbl/string.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <unittest/unittest.h>
#include <zircon/process.h>
namespace {
constexpr uint8_t kBlockSize = 5;
// Mock device based on code from ums-function.c
union usb_descriptor {
usb_interface_descriptor_t interface;
usb_endpoint_descriptor_t endpoint;
static constexpr usb_descriptor Create(usb_interface_descriptor_t descriptor) {
usb_descriptor retval = {};
retval.interface = descriptor;
return retval;
}
static constexpr usb_descriptor Create(usb_endpoint_descriptor_t descriptor) {
usb_descriptor retval = {};
retval.endpoint = descriptor;
return retval;
}
} __PACKED;
const usb_descriptor kDescriptors[] = {
// Interface descriptor
usb_descriptor::Create({sizeof(usb_descriptor), USB_DT_INTERFACE, 0, 0, 2, 8, 7, 0x50, 0}),
// IN endpoint
usb_descriptor::Create(usb_endpoint_descriptor_t(
{sizeof(usb_descriptor), USB_DT_ENDPOINT, USB_DIR_IN, USB_ENDPOINT_BULK, 64, 0})),
// OUT endpoint
usb_descriptor::Create(usb_endpoint_descriptor_t(
{sizeof(usb_descriptor), USB_DT_ENDPOINT, USB_DIR_OUT, USB_ENDPOINT_BULK, 64, 0}))};
struct Packet;
struct Packet : fbl::DoublyLinkedListable<fbl::RefPtr<Packet>>, fbl::RefCounted<Packet> {
explicit Packet(fbl::Array<unsigned char>&& source) { data = std::move(source); }
fbl::Array<unsigned char> data;
};
struct Context {
Context* parent;
ums::UmsBlockDevice* block_device;
ums::UsbMassStorageDevice* ums_device;
uint32_t desired_proto;
usb_protocol_t proto;
fbl::DoublyLinkedList<fbl::RefPtr<Packet>> pending_packets;
ums_csw_t csw;
const usb_descriptor* descs;
size_t desc_length;
size_t block_devs;
ums::UmsBlockDevice* devices[4];
sync_completion_t completion;
zx_status_t status;
block_op_t* op;
uint64_t transfer_offset;
uint64_t transfer_blocks;
uint8_t transfer_type;
uint8_t transfer_lun;
size_t pending_write;
fbl::RefPtr<Packet> last_transfer;
};
class Binder : public fake_ddk::Bind {
public:
zx_status_t DeviceGetProtocol(const zx_device_t* device, uint32_t proto_id, void* protocol) {
auto context = reinterpret_cast<const Context*>(device);
if (proto_id == context->desired_proto) {
*reinterpret_cast<usb_protocol_t*>(protocol) = context->proto;
return ZX_OK;
}
return ZX_ERR_PROTOCOL_NOT_SUPPORTED;
}
zx_status_t DeviceRemove(zx_device_t* device) {
Context* ctx = reinterpret_cast<Context*>(device);
delete ctx;
return ZX_OK;
}
zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
zx_device_t** out) {
Context* context = reinterpret_cast<Context*>(parent);
if (context->parent) {
Context* parent = context->parent;
parent->devices[parent->block_devs] =
reinterpret_cast<ums::UmsBlockDevice*>(args->ctx);
if ((++parent->block_devs) == 3) {
sync_completion_signal(&parent->completion);
}
}
Context* outctx = new Context();
outctx->parent = context;
outctx->block_device = reinterpret_cast<ums::UmsBlockDevice*>(args->ctx);
*out = reinterpret_cast<zx_device_t*>(outctx);
return ZX_OK;
}
};
static size_t GetDescriptorLength(void* ctx) {
Context* context = reinterpret_cast<Context*>(ctx);
return context->desc_length;
}
static void GetDescriptors(void* ctx, void* buffer, size_t size, size_t* outsize) {
Context* context = reinterpret_cast<Context*>(ctx);
*outsize = context->desc_length > size ? size : context->desc_length;
memcpy(buffer, context->descs, *outsize);
}
static zx_status_t ControlIn(void* ctx, uint8_t request_type, uint8_t request, uint16_t value,
uint16_t index, int64_t timeout, void* out_read_buffer,
size_t read_size, size_t* out_read_actual) {
switch (request) {
case USB_REQ_GET_MAX_LUN: {
if (!read_size) {
*out_read_actual = 0;
return ZX_OK;
}
*reinterpret_cast<unsigned char*>(out_read_buffer) = 3;
*out_read_actual = 1;
return ZX_OK;
}
default:
return ZX_ERR_IO_REFUSED;
}
}
static size_t GetMaxTransferSize(void* ctx, uint8_t ep) {
switch (ep) {
case USB_DIR_OUT:
__FALLTHROUGH;
case USB_DIR_IN:
// 10MB transfer size (to test large transfers)
// (is this even possible in real hardware?)
return 1000 * 1000 * 10;
default:
return 0;
}
}
static size_t GetRequestSize(void* ctx) {
return sizeof(usb_request_t);
}
static void RequestQueue(void* ctx, usb_request_t* usb_request,
const usb_request_complete_t* complete_cb) {
Context* context = reinterpret_cast<Context*>(ctx);
if (context->pending_write) {
void* data;
usb_request_mmap(usb_request, &data);
memcpy(context->last_transfer->data.get(), data, context->pending_write);
context->pending_write = 0;
complete_cb->callback(complete_cb->ctx, usb_request);
return;
}
if ((usb_request->header.ep_address & USB_ENDPOINT_DIR_MASK) == USB_ENDPOINT_IN) {
if (context->pending_packets.begin() == context->pending_packets.end()) {
complete_cb->callback(complete_cb->ctx, usb_request);
} else {
auto packet = context->pending_packets.pop_front();
size_t len =
usb_request->size < packet->data.size() ? usb_request->size : packet->data.size();
usb_request_copy_to(usb_request, packet->data.get(), len, 0);
complete_cb->callback(complete_cb->ctx, usb_request);
}
return;
}
void* data;
usb_request_mmap(usb_request, &data);
uint32_t header;
memcpy(&header, data, sizeof(header));
header = le32toh(header);
switch (header) {
case CBW_SIGNATURE: {
ums_cbw_t cbw;
memcpy(&cbw, data, sizeof(cbw));
if (cbw.bCBWLUN > 3) {
complete_cb->callback(complete_cb->ctx, usb_request);
return;
}
auto DataTransfer = [&]() {
if ((context->transfer_offset == cbw.bCBWLUN) &&
(context->transfer_blocks == (cbw.bCBWLUN + 1U) * 65534U)) {
uint8_t opcode = cbw.CBWCB[0];
size_t transfer_length = context->transfer_blocks * kBlockSize;
fbl::Array<unsigned char> transfer(new unsigned char[transfer_length],
transfer_length);
context->last_transfer = fbl::MakeRefCounted<Packet>(std::move(transfer));
context->transfer_lun = cbw.bCBWLUN;
if ((opcode == UMS_READ10) || (opcode == UMS_READ12) || (opcode == UMS_READ16)) {
// Push reply
context->pending_packets.push_back(context->last_transfer);
} else {
if ((opcode == UMS_WRITE10) || (opcode == UMS_WRITE12) ||
(opcode == UMS_WRITE16)) {
context->pending_write = transfer_length;
}
}
// Push CSW
fbl::Array<unsigned char> csw(new unsigned char[sizeof(ums_csw_t)],
sizeof(ums_csw_t));
context->csw.dCSWDataResidue = 0;
context->csw.dCSWTag = cbw.dCBWTag;
context->csw.bmCSWStatus = CSW_SUCCESS;
memcpy(csw.get(), &context->csw, sizeof(context->csw));
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(csw)));
}
};
switch (cbw.CBWCB[0]) {
case UMS_WRITE16:
__FALLTHROUGH;
case UMS_READ16: {
scsi_command16_t cmd;
memcpy(&cmd, cbw.CBWCB, sizeof(cmd));
context->transfer_blocks = be32toh(cmd.length);
context->transfer_offset = be64toh(cmd.lba);
context->transfer_type = cbw.CBWCB[0];
DataTransfer();
complete_cb->callback(complete_cb->ctx, usb_request);
break;
}
case UMS_WRITE12:
__FALLTHROUGH;
case UMS_READ12: {
scsi_command12_t cmd;
memcpy(&cmd, cbw.CBWCB, sizeof(cmd));
context->transfer_blocks = be32toh(cmd.length);
context->transfer_offset = be32toh(cmd.lba);
context->transfer_type = cbw.CBWCB[0];
DataTransfer();
complete_cb->callback(complete_cb->ctx, usb_request);
break;
}
case UMS_WRITE10:
__FALLTHROUGH;
case UMS_READ10: {
scsi_command10_t cmd;
memcpy(&cmd, cbw.CBWCB, sizeof(cmd));
context->transfer_blocks =
static_cast<uint16_t>(cmd.length_lo) | (static_cast<uint16_t>(cmd.length_hi) << 8);
context->transfer_offset = be32toh(cmd.lba);
context->transfer_type = cbw.CBWCB[0];
DataTransfer();
complete_cb->callback(complete_cb->ctx, usb_request);
break;
}
case UMS_SYNCHRONIZE_CACHE: {
// Push CSW
fbl::Array<unsigned char> csw(new unsigned char[sizeof(ums_csw_t)], sizeof(ums_csw_t));
context->csw.dCSWDataResidue = 0;
context->csw.dCSWTag = cbw.dCBWTag;
context->csw.bmCSWStatus = CSW_SUCCESS;
context->transfer_lun = cbw.bCBWLUN;
context->transfer_type = cbw.CBWCB[0];
memcpy(csw.get(), &context->csw, sizeof(context->csw));
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(csw)));
complete_cb->callback(complete_cb->ctx, usb_request);
break;
}
case UMS_INQUIRY: {
scsi_command6_t cmd;
memcpy(&cmd, cbw.CBWCB, sizeof(cmd));
if (cmd.length == UMS_INQUIRY_TRANSFER_LENGTH) {
// Push reply
fbl::Array<unsigned char> reply(new unsigned char[UMS_INQUIRY_TRANSFER_LENGTH],
UMS_INQUIRY_TRANSFER_LENGTH);
reply[0] = 0; // Peripheral Device Type: Direct access block device
reply[1] = 0x80; // Removable
reply[2] = 6; // Version SPC-4
reply[3] = 0x12; // Response Data Format
memcpy(reply.get() + 8, "Google ", 8);
memcpy(reply.get() + 16, "Zircon UMS ", 16);
memcpy(reply.get() + 32, "1.00", 4);
memset(reply.get(), 0, UMS_INQUIRY_TRANSFER_LENGTH);
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(reply)));
// Push CSW
fbl::Array<unsigned char> csw(new unsigned char[sizeof(ums_csw_t)],
sizeof(ums_csw_t));
context->csw.dCSWDataResidue = 0;
context->csw.dCSWTag = cbw.dCBWTag;
context->csw.bmCSWStatus = CSW_SUCCESS;
memcpy(csw.get(), &context->csw, sizeof(context->csw));
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(csw)));
}
complete_cb->callback(complete_cb->ctx, usb_request);
break;
}
case UMS_TEST_UNIT_READY: {
// Push CSW
fbl::Array<unsigned char> csw(new unsigned char[sizeof(ums_csw_t)], sizeof(ums_csw_t));
context->csw.dCSWDataResidue = 0;
context->csw.dCSWTag = cbw.dCBWTag;
context->csw.bmCSWStatus = CSW_SUCCESS;
memcpy(csw.get(), &context->csw, sizeof(context->csw));
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(csw)));
complete_cb->callback(complete_cb->ctx, usb_request);
break;
}
case UMS_READ_CAPACITY16: {
if (cbw.bCBWLUN == 3) {
// Push reply
fbl::Array<unsigned char> reply(new unsigned char[sizeof(scsi_read_capacity_16_t)],
sizeof(scsi_read_capacity_16_t));
scsi_read_capacity_16_t scsi;
scsi.block_length = htobe32(kBlockSize);
scsi.lba = htobe64((976562L * (1 + cbw.bCBWLUN)) + UINT32_MAX);
memcpy(reply.get(), &scsi, sizeof(scsi));
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(reply)));
// Push CSW
fbl::Array<unsigned char> csw(new unsigned char[sizeof(ums_csw_t)],
sizeof(ums_csw_t));
context->csw.dCSWDataResidue = 0;
context->csw.dCSWTag = cbw.dCBWTag;
context->csw.bmCSWStatus = CSW_SUCCESS;
memcpy(csw.get(), &context->csw, sizeof(context->csw));
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(csw)));
}
complete_cb->callback(complete_cb->ctx, usb_request);
break;
}
case UMS_READ_CAPACITY10: {
// Push reply
fbl::Array<unsigned char> reply(new unsigned char[sizeof(scsi_read_capacity_10_t)],
sizeof(scsi_read_capacity_10_t));
scsi_read_capacity_10_t scsi;
scsi.block_length = htobe32(kBlockSize);
scsi.lba = htobe32(976562 * (1 + cbw.bCBWLUN));
if (cbw.bCBWLUN == 3) {
scsi.lba = -1;
}
memcpy(reply.get(), &scsi, sizeof(scsi));
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(reply)));
// Push CSW
fbl::Array<unsigned char> csw(new unsigned char[sizeof(ums_csw_t)], sizeof(ums_csw_t));
context->csw.dCSWDataResidue = 0;
context->csw.dCSWTag = cbw.dCBWTag;
context->csw.bmCSWStatus = CSW_SUCCESS;
memcpy(csw.get(), &context->csw, sizeof(context->csw));
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(csw)));
complete_cb->callback(complete_cb->ctx, usb_request);
break;
}
case UMS_MODE_SENSE6: {
scsi_mode_sense_6_command_t cmd;
memcpy(&cmd, cbw.CBWCB, sizeof(cmd));
// Push reply
switch (cmd.page) {
case 0x3F: {
fbl::Array<unsigned char> reply(new unsigned char[sizeof(scsi_read_capacity_10_t)],
sizeof(scsi_read_capacity_10_t));
scsi_mode_sense_6_data_t scsi = {};
memcpy(reply.get(), &scsi, sizeof(scsi));
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(reply)));
// Push CSW
fbl::Array<unsigned char> csw(new unsigned char[sizeof(ums_csw_t)],
sizeof(ums_csw_t));
context->csw.dCSWDataResidue = 0;
context->csw.dCSWTag = cbw.dCBWTag;
context->csw.bmCSWStatus = CSW_SUCCESS;
memcpy(csw.get(), &context->csw, sizeof(context->csw));
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(csw)));
complete_cb->callback(complete_cb->ctx, usb_request);
break;
}
case 0x08: {
fbl::Array<unsigned char> reply(new unsigned char[20], 20);
memset(reply.get(), 0, 20);
reply[6] = 1 << 2;
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(reply)));
// Push CSW
fbl::Array<unsigned char> csw(new unsigned char[sizeof(ums_csw_t)],
sizeof(ums_csw_t));
context->csw.dCSWDataResidue = 0;
context->csw.dCSWTag = cbw.dCBWTag;
context->csw.bmCSWStatus = CSW_SUCCESS;
memcpy(csw.get(), &context->csw, sizeof(context->csw));
context->pending_packets.push_back(fbl::MakeRefCounted<Packet>(std::move(csw)));
complete_cb->callback(complete_cb->ctx, usb_request);
break;
}
default:
complete_cb->callback(complete_cb->ctx, usb_request);
}
break;
}
}
break;
}
default:
context->csw.bmCSWStatus = CSW_FAILED;
usb_request->response.status = ZX_ERR_IO;
complete_cb->callback(complete_cb->ctx, usb_request);
}
}
static void CompletionCallback(void* ctx, zx_status_t status, block_op_t* op) {
Context* context = reinterpret_cast<Context*>(ctx);
context->status = status;
context->op = op;
sync_completion_signal(&context->completion);
}
static zx_status_t Setup(Context* context, ums::UsbMassStorageDevice* dev,
usb_protocol_ops_t* ops) {
zx_status_t status = ZX_OK;
// Device paramaters for physical (parent) device
context->parent = nullptr;
context->ums_device = dev;
context->block_devs = 0;
context->pending_write = 0;
context->csw.dCSWSignature = htole32(CSW_SIGNATURE);
context->csw.bmCSWStatus = CSW_SUCCESS;
context->descs = kDescriptors;
context->desc_length = sizeof(kDescriptors);
context->desired_proto = ZX_PROTOCOL_USB;
// Binding of ops to enable communication between the virtual device and UMS driver
context->proto.ctx = context;
context->proto.ops = ops;
ops->get_descriptors_length = GetDescriptorLength;
ops->get_descriptors = GetDescriptors;
ops->get_request_size = GetRequestSize;
ops->request_queue = RequestQueue;
ops->get_max_transfer_size = GetMaxTransferSize;
ops->control_in = ControlIn;
// Driver initialization
status = dev->Init();
if (status != ZX_OK) {
return status;
}
sync_completion_wait(&context->completion, ZX_TIME_INFINITE);
sync_completion_reset(&context->completion);
return status;
}
// UMS read test
// This test validates the read functionality on multiple LUNS
// of a USB mass storage device.
bool UmsTestRead() {
// Setup
BEGIN_TEST;
Binder bind;
Context parent_dev;
usb_protocol_ops_t ops;
ums::UsbMassStorageDevice dev(reinterpret_cast<zx_device_t*>(&parent_dev));
Setup(&parent_dev, &dev, &ops);
zx_handle_t vmo;
uint64_t size;
zx_vaddr_t mapped;
// VMO creation to read data into
EXPECT_EQ(ZX_OK, zx_vmo_create(1000 * 1000 * 10, 0, &vmo), "Failed to create VMO");
EXPECT_EQ(ZX_OK, zx_vmo_get_size(vmo, &size), "Failed to get size of VMO");
EXPECT_EQ(ZX_OK, zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ, 0, vmo, 0, size, &mapped),
"Failed to map VMO");
// Perform read transactions
for (uint32_t i = 0; i < parent_dev.block_devs; i++) {
ums::Transaction transaction;
transaction.op.command = BLOCK_OP_READ;
transaction.op.rw.offset_dev = i;
transaction.op.rw.length = (i + 1) * 65534;
transaction.op.rw.offset_vmo = 0;
transaction.op.rw.vmo = vmo;
transaction.cookie = &parent_dev;
transaction.dev = parent_dev.devices[i];
transaction.completion_cb = CompletionCallback;
dev.QueueTransaction(&transaction);
sync_completion_wait(&parent_dev.completion, ZX_TIME_INFINITE);
sync_completion_reset(&parent_dev.completion);
uint8_t xfer_type = i == 0 ? UMS_READ10 : i == 3 ? UMS_READ16 : UMS_READ12;
EXPECT_EQ(i, parent_dev.transfer_lun);
EXPECT_EQ(xfer_type, parent_dev.transfer_type);
EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(mapped), parent_dev.last_transfer->data.get(),
parent_dev.last_transfer->data.size()));
}
// Unbind
dev.DdkUnbind();
EXPECT_EQ(4, parent_dev.block_devs);
END_TEST;
}
// This test validates the write functionality on multiple LUNS
// of a USB mass storage device.
bool UmsTestWrite() {
// Setup
BEGIN_TEST;
Binder bind;
Context parent_dev;
usb_protocol_ops_t ops;
ums::UsbMassStorageDevice dev(reinterpret_cast<zx_device_t*>(&parent_dev));
Setup(&parent_dev, &dev, &ops);
zx_handle_t vmo;
uint64_t size;
zx_vaddr_t mapped;
// VMO creation to transfer from
EXPECT_EQ(ZX_OK, zx_vmo_create(1000 * 1000 * 10, 0, &vmo), "Failed to create VMO");
EXPECT_EQ(ZX_OK, zx_vmo_get_size(vmo, &size), "Failed to get size of VMO");
EXPECT_EQ(ZX_OK, zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo, 0,
size, &mapped),
"Failed to map VMO");
// Add "entropy" for write operation
for (size_t i = 0; i < size / sizeof(size_t); i++) {
reinterpret_cast<size_t*>(mapped)[i] = i;
}
// Perform write transactions
for (uint32_t i = 0; i < parent_dev.block_devs; i++) {
ums::Transaction transaction;
transaction.op.command = BLOCK_OP_WRITE;
transaction.op.rw.offset_dev = i;
transaction.op.rw.length = (i + 1) * 65534;
transaction.op.rw.offset_vmo = 0;
transaction.op.rw.vmo = vmo;
transaction.cookie = &parent_dev;
transaction.dev = parent_dev.devices[i];
transaction.completion_cb = CompletionCallback;
dev.QueueTransaction(&transaction);
sync_completion_wait(&parent_dev.completion, ZX_TIME_INFINITE);
sync_completion_reset(&parent_dev.completion);
uint8_t xfer_type = i == 0 ? UMS_WRITE10 : i == 3 ? UMS_WRITE16 : UMS_WRITE12;
EXPECT_EQ(i, parent_dev.transfer_lun);
EXPECT_EQ(xfer_type, parent_dev.transfer_type);
EXPECT_EQ(0, memcmp(reinterpret_cast<void*>(mapped), parent_dev.last_transfer->data.get(),
transaction.op.rw.length * kBlockSize));
}
// Unbind
dev.DdkUnbind();
EXPECT_EQ(4, parent_dev.block_devs);
END_TEST;
}
// This test validates the flush functionality on multiple LUNS
// of a USB mass storage device.
bool UmsTestFlush() {
// Setup
BEGIN_TEST;
Binder bind;
Context parent_dev;
ums::UsbMassStorageDevice dev(reinterpret_cast<zx_device_t*>(&parent_dev));
usb_protocol_ops_t ops;
Setup(&parent_dev, &dev, &ops);
// Perform flush transactions
for (uint32_t i = 0; i < parent_dev.block_devs; i++) {
ums::Transaction transaction;
transaction.op.command = BLOCK_OP_FLUSH;
transaction.cookie = &parent_dev;
transaction.dev = parent_dev.devices[i];
transaction.completion_cb = CompletionCallback;
dev.QueueTransaction(&transaction);
sync_completion_wait(&parent_dev.completion, ZX_TIME_INFINITE);
sync_completion_reset(&parent_dev.completion);
uint8_t xfer_type = UMS_SYNCHRONIZE_CACHE;
EXPECT_EQ(i, parent_dev.transfer_lun);
EXPECT_EQ(xfer_type, parent_dev.transfer_type);
}
// Unbind
dev.DdkUnbind();
EXPECT_EQ(4, parent_dev.block_devs);
END_TEST;
}
} // namespace
BEGIN_TEST_CASE(usb_mass_storage_tests)
RUN_NAMED_TEST("USB mass storage device test (read)", UmsTestRead)
RUN_NAMED_TEST("USB mass storage device test (write)", UmsTestWrite)
RUN_NAMED_TEST("USB mass storage device test (flush)", UmsTestFlush)
END_TEST_CASE(usb_mass_storage_tests);