blob: 7627d13ba499f2cd3f2ea0a3e87281b19b75af0c [file] [log] [blame]
// Copyright 2016 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 <endian.h>
#include <stdio.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/hw/usb/ums.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include "block.h"
// comment the next line if you don't want debug messages
#define DEBUG 0
#ifdef DEBUG
#define DEBUG_PRINT(x) printf x
#else
#define DEBUG_PRINT(x) \
do { \
} while (0)
#endif
namespace {
void ReqComplete(void* ctx, usb_request_t* req) {
if (ctx) {
sync_completion_signal(static_cast<sync_completion_t*>(ctx));
}
}
} // namespace
namespace ums {
class WaiterImpl : public WaiterInterface {
public:
zx_status_t Wait(sync_completion_t* completion, zx_duration_t duration) {
return sync_completion_wait(completion, duration);
}
};
void UsbMassStorageDevice::QueueTransaction(Transaction* txn) {
{
fbl::AutoLock l(&txn_lock_);
list_add_tail(&queued_txns_, &txn->node);
}
sync_completion_signal(&txn_completion_);
}
void UsbMassStorageDevice::DdkRelease() {
Release();
delete this;
}
void UsbMassStorageDevice::Release() {
if (cbw_req_) {
usb_request_release(cbw_req_);
}
if (data_req_) {
usb_request_release(data_req_);
}
if (csw_req_) {
usb_request_release(csw_req_);
}
if (data_transfer_req_) {
// release_frees is indirectly cleared by DataTransfer; set it again here so that
// data_transfer_req_ is freed by usb_request_release.
data_transfer_req_->release_frees = true;
usb_request_release(data_transfer_req_);
}
if (worker_thread_.has_value() && worker_thread_->joinable()) {
worker_thread_->join();
}
}
void UsbMassStorageDevice::DdkUnbind(ddk::UnbindTxn txn) {
// terminate our worker thread
{
fbl::AutoLock l(&txn_lock_);
dead_ = true;
}
sync_completion_signal(&txn_completion_);
// wait for worker thread to finish before removing devices
if (worker_thread_.has_value() && worker_thread_->joinable()) {
worker_thread_->join();
}
// Wait for remaining requests to complete
while (pending_requests_.load()) {
waiter_->Wait(&txn_completion_, ZX_SEC(1));
}
txn.Reply();
}
void UsbMassStorageDevice::RequestQueue(usb_request_t* request,
const usb_request_complete_t* completion) {
fbl::AutoLock l(&txn_lock_);
pending_requests_++;
UsbRequestContext context;
context.completion = *completion;
usb_request_complete_t complete;
complete.callback = [](void* ctx, usb_request_t* req) {
UsbRequestContext context;
memcpy(&context,
reinterpret_cast<unsigned char*>(req) +
reinterpret_cast<UsbMassStorageDevice*>(ctx)->parent_req_size_,
sizeof(context));
reinterpret_cast<UsbMassStorageDevice*>(ctx)->pending_requests_--;
context.completion.callback(context.completion.ctx, req);
};
complete.ctx = this;
memcpy(reinterpret_cast<unsigned char*>(request) + parent_req_size_, &context, sizeof(context));
usb_.RequestQueue(request, &complete);
}
// Performs the object initialization.
zx_status_t UsbMassStorageDevice::Init(bool is_test_mode) {
dead_ = false;
is_test_mode_ = is_test_mode;
zxlogf(INFO, "UMS: parent: '%s'", device_get_name(parent()));
// Add root device, which will contain block devices for logical units
zx_status_t status = DdkAdd("ums", DEVICE_ADD_NON_BINDABLE);
if (status != ZX_OK) {
delete this;
return status;
}
return status;
}
void UsbMassStorageDevice::DdkInit(ddk::InitTxn txn) {
usb::UsbDevice usb(parent());
if (!usb.is_valid()) {
txn.Reply(ZX_ERR_PROTOCOL_NOT_SUPPORTED);
return;
}
// find our endpoints
std::optional<usb::InterfaceList> interfaces;
zx_status_t status = usb::InterfaceList::Create(usb, true, &interfaces);
if (status != ZX_OK) {
txn.Reply(status);
return;
}
auto interface = interfaces->begin();
if (interface == interfaces->end()) {
txn.Reply(ZX_ERR_NOT_SUPPORTED);
return;
}
const usb_interface_descriptor_t* interface_descriptor = interface->descriptor();
// Since interface != interface->end(), interface_descriptor is guaranteed not null.
ZX_DEBUG_ASSERT(interface_descriptor);
uint8_t interface_number = interface_descriptor->bInterfaceNumber;
uint8_t bulk_in_addr = 0;
uint8_t bulk_out_addr = 0;
size_t bulk_in_max_packet = 0;
size_t bulk_out_max_packet = 0;
if (interface_descriptor->bNumEndpoints < 2) {
DEBUG_PRINT(
("UMS:ums_bind wrong number of endpoints: %d\n", interface_descriptor->bNumEndpoints));
txn.Reply(ZX_ERR_NOT_SUPPORTED);
return;
}
for (auto ep_itr : interfaces->begin()->GetEndpointList()) {
const usb_endpoint_descriptor_t* endp = &ep_itr.descriptor;
if (usb_ep_direction(endp) == USB_ENDPOINT_OUT) {
if (usb_ep_type(endp) == USB_ENDPOINT_BULK) {
bulk_out_addr = endp->bEndpointAddress;
bulk_out_max_packet = usb_ep_max_packet(endp);
}
} else {
if (usb_ep_type(endp) == USB_ENDPOINT_BULK) {
bulk_in_addr = endp->bEndpointAddress;
bulk_in_max_packet = usb_ep_max_packet(endp);
}
}
}
if (!is_test_mode_ && (!bulk_in_max_packet || !bulk_out_max_packet)) {
DEBUG_PRINT(("UMS:ums_bind could not find endpoints\n"));
txn.Reply(ZX_ERR_NOT_SUPPORTED);
return;
}
uint8_t max_lun;
size_t out_length;
status = usb.ControlIn(USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, USB_REQ_GET_MAX_LUN,
0x00, 0x00, ZX_TIME_INFINITE, &max_lun, sizeof(max_lun), &out_length);
if (status == ZX_ERR_IO_REFUSED) {
// Devices that do not support multiple LUNS may stall this command.
// See USB Mass Storage Class Spec. 3.2 Get Max LUN.
// Clear the stall.
usb.ResetEndpoint(0);
zxlogf(INFO, "Device does not support multiple LUNs");
max_lun = 0;
} else if (status != ZX_OK) {
txn.Reply(status);
return;
} else if (out_length != sizeof(max_lun)) {
txn.Reply(ZX_ERR_BAD_STATE);
return;
}
fbl::AllocChecker checker;
fbl::RefPtr<UmsBlockDevice>* raw_array;
raw_array = new (&checker) fbl::RefPtr<UmsBlockDevice>[max_lun + 1];
if (!checker.check()) {
txn.Reply(ZX_ERR_NO_MEMORY);
return;
}
block_devs_ = fbl::Array(raw_array, max_lun + 1);
DEBUG_PRINT(("UMS:Max lun is: %u\n", max_lun));
max_lun_ = max_lun;
for (uint8_t lun = 0; lun <= max_lun; lun++) {
auto dev = fbl::MakeRefCountedChecked<UmsBlockDevice>(
&checker, zxdev(), lun, [this](ums::Transaction* txn) { QueueTransaction(txn); });
if (!checker.check()) {
txn.Reply(ZX_ERR_NO_MEMORY);
return;
}
block_devs_[lun] = dev;
}
list_initialize(&queued_txns_);
sync_completion_reset(&txn_completion_);
usb_ = usb;
bulk_in_addr_ = bulk_in_addr;
bulk_out_addr_ = bulk_out_addr;
bulk_in_max_packet_ = bulk_in_max_packet;
bulk_out_max_packet_ = bulk_out_max_packet;
interface_number_ = interface_number;
size_t max_in = usb.GetMaxTransferSize(bulk_in_addr);
size_t max_out = usb.GetMaxTransferSize(bulk_out_addr);
max_transfer_ = (max_in < max_out ? max_in : max_out);
parent_req_size_ = usb.GetRequestSize();
ZX_DEBUG_ASSERT(parent_req_size_ != 0);
size_t usb_request_size = parent_req_size_ + sizeof(UsbRequestContext);
status = usb_request_alloc(&cbw_req_, sizeof(ums_cbw_t), bulk_out_addr, usb_request_size);
if (status != ZX_OK) {
txn.Reply(status);
return;
}
status = usb_request_alloc(&data_req_, PAGE_SIZE, bulk_in_addr, usb_request_size);
if (status != ZX_OK) {
txn.Reply(status);
return;
}
status = usb_request_alloc(&csw_req_, sizeof(ums_csw_t), bulk_in_addr, usb_request_size);
if (status != ZX_OK) {
txn.Reply(status);
return;
}
status = usb_request_alloc(&data_transfer_req_, 0, bulk_in_addr, usb_request_size);
if (status != ZX_OK) {
txn.Reply(status);
return;
}
tag_send_ = tag_receive_ = 8;
if (status != ZX_OK) {
txn.Reply(status);
return;
}
worker_thread_.emplace(
[this, init_txn = std::move(txn)]() mutable { WorkerThread(std::move(init_txn)); });
}
zx_status_t UsbMassStorageDevice::Reset() {
// UMS Reset Recovery. See section 5.3.4 of
// "Universal Serial Bus Mass Storage Class Bulk-Only Transport"
DEBUG_PRINT(("UMS: performing reset recovery\n"));
// Step 1: Send Bulk-Only Mass Storage Reset
zx_status_t status =
usb_.ControlOut(USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, USB_REQ_RESET, 0,
interface_number_, ZX_TIME_INFINITE, NULL, 0);
usb_protocol_t usb;
usb_.GetProto(&usb);
if (status != ZX_OK) {
DEBUG_PRINT(("UMS: USB_REQ_RESET failed %d\n", status));
return status;
}
// Step 2: Clear Feature HALT to the Bulk-In endpoint
constexpr uint8_t request_type = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT;
status = usb_.ClearFeature(request_type, USB_ENDPOINT_HALT, bulk_in_addr_, ZX_TIME_INFINITE);
if (status != ZX_OK) {
DEBUG_PRINT(("UMS: clear endpoint halt failed %d\n", status));
return status;
}
// Step 3: Clear Feature HALT to the Bulk-Out endpoint
status = usb_.ClearFeature(request_type, USB_ENDPOINT_HALT, bulk_out_addr_, ZX_TIME_INFINITE);
if (status != ZX_OK) {
DEBUG_PRINT(("UMS: clear endpoint halt failed %d\n", status));
return status;
}
return ZX_OK;
}
zx_status_t UsbMassStorageDevice::SendCbw(uint8_t lun, uint32_t transfer_length, uint8_t flags,
uint8_t command_len, void* command) {
usb_request_t* req = cbw_req_;
ums_cbw_t* cbw;
zx_status_t status = usb_request_mmap(req, (void**)&cbw);
if (status != ZX_OK) {
DEBUG_PRINT(("UMS: usb request mmap failed: %d\n", status));
return status;
}
memset(cbw, 0, sizeof(*cbw));
cbw->dCBWSignature = htole32(CBW_SIGNATURE);
cbw->dCBWTag = htole32(tag_send_++);
cbw->dCBWDataTransferLength = htole32(transfer_length);
cbw->bmCBWFlags = flags;
cbw->bCBWLUN = lun;
cbw->bCBWCBLength = command_len;
// copy command_len bytes from the command passed in into the command_len
memcpy(cbw->CBWCB, command, command_len);
sync_completion_t completion;
usb_request_complete_t complete = {
.callback = ReqComplete,
.ctx = &completion,
};
RequestQueue(req, &complete);
waiter_->Wait(&completion, ZX_TIME_INFINITE);
return req->response.status;
}
zx_status_t UsbMassStorageDevice::ReadCsw(uint32_t* out_residue) {
sync_completion_t completion;
usb_request_complete_t complete = {
.callback = ReqComplete,
.ctx = &completion,
};
usb_request_t* csw_request = csw_req_;
RequestQueue(csw_request, &complete);
waiter_->Wait(&completion, ZX_TIME_INFINITE);
csw_status_t csw_error = VerifyCsw(csw_request, out_residue);
if (csw_error == CSW_SUCCESS) {
return ZX_OK;
} else if (csw_error == CSW_FAILED) {
return ZX_ERR_BAD_STATE;
} else {
// FIXME - best way to handle this?
// print error and then reset device due to it
DEBUG_PRINT(
("UMS: CSW verify returned error. Check ums-hw.h csw_status_t for enum = %d\n", csw_error));
Reset();
return ZX_ERR_INTERNAL;
}
}
csw_status_t UsbMassStorageDevice::VerifyCsw(usb_request_t* csw_request, uint32_t* out_residue) {
ums_csw_t csw = {};
__UNUSED size_t result = usb_request_copy_from(csw_request, &csw, sizeof(csw), 0);
// check signature is "USBS"
if (letoh32(csw.dCSWSignature) != CSW_SIGNATURE) {
DEBUG_PRINT(("UMS:invalid csw sig: %08x \n", letoh32(csw.dCSWSignature)));
return CSW_INVALID;
}
// check if tag matches the tag of last CBW
if (letoh32(csw.dCSWTag) != tag_receive_++) {
DEBUG_PRINT(("UMS:csw tag mismatch, expected:%08x got in csw:%08x \n", tag_receive_ - 1,
letoh32(csw.dCSWTag)));
return CSW_TAG_MISMATCH;
}
// check if success is true or not?
if (csw.bmCSWStatus == CSW_FAILED) {
return CSW_FAILED;
} else if (csw.bmCSWStatus == CSW_PHASE_ERROR) {
return CSW_PHASE_ERROR;
}
if (out_residue) {
*out_residue = letoh32(csw.dCSWDataResidue);
}
return CSW_SUCCESS;
}
void UsbMassStorageDevice::QueueRead(uint16_t transfer_length) {
// Read response code from device
usb_request_t* read_request = data_req_;
read_request->header.length = transfer_length;
usb_request_complete_t complete = {
.callback = ReqComplete,
.ctx = NULL,
};
RequestQueue(read_request, &complete);
}
zx_status_t UsbMassStorageDevice::ReadSync(uint16_t transfer_length) {
// Read response code from device
usb_request_t* read_request = data_req_;
read_request->header.length = transfer_length;
sync_completion_t completion;
usb_request_complete_t complete = {
.callback = ReqComplete,
.ctx = &completion,
};
RequestQueue(read_request, &complete);
sync_completion_wait(&completion, ZX_TIME_INFINITE);
return read_request->response.status;
}
zx_status_t UsbMassStorageDevice::Inquiry(uint8_t lun, uint8_t* out_data) {
// CBW Configuration
scsi_command6_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_INQUIRY;
command.length = UMS_INQUIRY_TRANSFER_LENGTH;
SendCbw(lun, UMS_INQUIRY_TRANSFER_LENGTH, USB_DIR_IN, sizeof(command), &command);
// read inquiry response
QueueRead(UMS_INQUIRY_TRANSFER_LENGTH);
// wait for CSW
zx_status_t status = ReadCsw(NULL);
if (status == ZX_OK) {
memset(out_data, 0, UMS_INQUIRY_TRANSFER_LENGTH);
__UNUSED auto result =
usb_request_copy_from(data_req_, out_data, UMS_INQUIRY_TRANSFER_LENGTH, 0);
}
return status;
}
zx_status_t UsbMassStorageDevice::TestUnitReady(uint8_t lun) {
// CBW Configuration
scsi_command6_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_TEST_UNIT_READY;
SendCbw(lun, 0, USB_DIR_IN, sizeof(command), &command);
// wait for CSW
return ReadCsw(NULL);
}
zx_status_t UsbMassStorageDevice::RequestSense(uint8_t lun, uint8_t* out_data) {
// CBW Configuration
scsi_command6_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_REQUEST_SENSE;
command.length = UMS_REQUEST_SENSE_TRANSFER_LENGTH;
SendCbw(lun, UMS_REQUEST_SENSE_TRANSFER_LENGTH, USB_DIR_IN, sizeof(command), &command);
// Read response code from device
QueueRead(UMS_REQUEST_SENSE_TRANSFER_LENGTH);
// wait for CSW
zx_status_t status = ReadCsw(NULL);
if (status == ZX_OK) {
memset(out_data, 0, UMS_REQUEST_SENSE_TRANSFER_LENGTH);
__UNUSED auto result =
usb_request_copy_from(data_req_, out_data, UMS_REQUEST_SENSE_TRANSFER_LENGTH, 0);
}
return status;
}
zx_status_t UsbMassStorageDevice::ReadCapacity(uint8_t lun, scsi_read_capacity_10_t* out_data) {
// CBW Configuration
scsi_command10_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_READ_CAPACITY10;
SendCbw(lun, sizeof(*out_data), USB_DIR_IN, sizeof(command), &command);
// read capacity10 response
QueueRead(sizeof(*out_data));
zx_status_t status = ReadCsw(NULL);
if (status == ZX_OK) {
memset(out_data, 0, sizeof(*out_data));
__UNUSED auto result = usb_request_copy_from(data_req_, out_data, sizeof(*out_data), 0);
}
return status;
}
zx_status_t UsbMassStorageDevice::ReadCapacity(uint8_t lun, scsi_read_capacity_16_t* out_data) {
// CBW Configuration
scsi_command16_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_READ_CAPACITY16;
// service action = 10, not sure what that means
command.misc = 0x10;
command.length = sizeof(*out_data);
SendCbw(lun, sizeof(*out_data), USB_DIR_IN, sizeof(command), &command);
// read capacity16 response
QueueRead(sizeof(*out_data));
zx_status_t status = ReadCsw(NULL);
if (status == ZX_OK) {
memset(out_data, 0, sizeof(*out_data));
__UNUSED auto result = usb_request_copy_from(data_req_, out_data, sizeof(*out_data), 0);
}
return status;
}
zx_status_t UsbMassStorageDevice::ModeSense(uint8_t lun, uint8_t page, void* data,
uint8_t transfer_length) {
// CBW Configuration
scsi_mode_sense_6_command_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_MODE_SENSE6;
command.page = page; // all pages, current values
command.allocation_length = transfer_length;
// Per section 6.5 of UMS specification version 1.0
// the device should report any errors in the CSW stage,
// which seems to suggest that stalling here is out-of-spec.
// Some devices that we tested with do stall the CBW or data transfer stage,
// so to accomodate those devices we consider the transfer to have ended (with an error)
// when we receive a stall condition from the device.
if (SendCbw(lun, transfer_length, USB_DIR_IN, sizeof(command), &command) == ZX_ERR_IO_REFUSED) {
return ZX_ERR_IO_REFUSED;
}
// read mode sense response
if (ReadSync(transfer_length) == ZX_ERR_IO_REFUSED) {
return ZX_ERR_IO_REFUSED;
}
zx_status_t status = ReadCsw(NULL);
if (status == ZX_OK) {
memset(data, 0, transfer_length);
__UNUSED auto result = usb_request_copy_from(data_req_, data, transfer_length, 0);
}
return status;
}
zx_status_t UsbMassStorageDevice::ModeSense(uint8_t lun, scsi_mode_sense_6_data_t* out_data) {
// CBW Configuration
scsi_mode_sense_6_command_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_MODE_SENSE6;
command.page = 0x3F; // all pages, current values
command.allocation_length = sizeof(*out_data);
SendCbw(lun, sizeof(*out_data), USB_DIR_IN, sizeof(command), &command);
// read mode sense response
QueueRead(sizeof(*out_data));
zx_status_t status = ReadCsw(NULL);
if (status == ZX_OK) {
memset(out_data, 0, sizeof(*out_data));
__UNUSED auto result = usb_request_copy_from(data_req_, out_data, sizeof(*out_data), 0);
}
return status;
}
zx_status_t UsbMassStorageDevice::DataTransfer(Transaction* txn, zx_off_t offset, size_t length,
uint8_t ep_address) {
usb_request_t* req = data_transfer_req_;
zx_status_t status = usb_request_init(req, txn->op.rw.vmo, offset, length, ep_address);
if (status != ZX_OK) {
return status;
}
sync_completion_t completion;
usb_request_complete_t complete = {
.callback = ReqComplete,
.ctx = &completion,
};
RequestQueue(req, &complete);
waiter_->Wait(&completion, ZX_TIME_INFINITE);
status = req->response.status;
if (status == ZX_OK && req->response.actual != length) {
status = ZX_ERR_IO;
}
usb_request_release(req);
return status;
}
zx_status_t UsbMassStorageDevice::Read(UmsBlockDevice* dev, Transaction* txn) {
const auto& params = dev->GetBlockDeviceParameters();
zx_off_t block_offset = txn->op.rw.offset_dev;
uint32_t num_blocks = txn->op.rw.length;
if ((block_offset >= params.total_blocks) ||
((params.total_blocks - block_offset) < num_blocks)) {
return ZX_ERR_OUT_OF_RANGE;
}
size_t block_size = params.block_size;
zx_off_t vmo_offset = txn->op.rw.offset_vmo * block_size;
size_t max_blocks = max_transfer_ / block_size;
zx_status_t status = ZX_OK;
while (status == ZX_OK && num_blocks > 0) {
size_t blocks = num_blocks;
if (blocks > max_blocks) {
blocks = max_blocks;
}
size_t length = blocks * block_size;
// CBW Configuration
// Need to use UMS_READ16 if block addresses are greater than 32 bit
if (params.total_blocks > UINT32_MAX) {
scsi_command16_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_READ16;
command.lba = htobe64(block_offset);
command.length = htobe32(static_cast<uint32_t>(blocks));
status =
SendCbw(params.lun, static_cast<uint32_t>(length), USB_DIR_IN, sizeof(command), &command);
} else if (blocks <= UINT16_MAX) {
scsi_command10_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_READ10;
command.lba = htobe32(static_cast<uint32_t>(block_offset));
command.length_hi = static_cast<uint8_t>(blocks >> 8);
command.length_lo = static_cast<uint8_t>(blocks & 0xFF);
status =
SendCbw(params.lun, static_cast<uint32_t>(length), USB_DIR_IN, sizeof(command), &command);
} else {
scsi_command12_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_READ12;
command.lba = htobe32(static_cast<uint32_t>(block_offset));
command.length = htobe32(static_cast<uint32_t>(blocks));
status =
SendCbw(params.lun, static_cast<uint32_t>(length), USB_DIR_IN, sizeof(command), &command);
}
if (status != ZX_OK) {
zxlogf(WARNING, "UMS: SendCbw during read failed with status %d\n", status);
return status;
}
status = DataTransfer(txn, vmo_offset, length, bulk_in_addr_);
if (status != ZX_OK) {
return status;
}
block_offset += blocks;
num_blocks -= static_cast<uint32_t>(blocks);
vmo_offset += (blocks * block_size);
// receive CSW
uint32_t residue;
status = ReadCsw(&residue);
if (status == ZX_OK && residue) {
zxlogf(ERROR, "unexpected residue in Read");
status = ZX_ERR_IO;
}
}
return status;
}
zx_status_t UsbMassStorageDevice::Write(UmsBlockDevice* dev, Transaction* txn) {
const auto& params = dev->GetBlockDeviceParameters();
zx_off_t block_offset = txn->op.rw.offset_dev;
uint32_t num_blocks = txn->op.rw.length;
if ((block_offset >= params.total_blocks) ||
((params.total_blocks - block_offset) < num_blocks)) {
return ZX_ERR_OUT_OF_RANGE;
}
size_t block_size = params.block_size;
zx_off_t vmo_offset = txn->op.rw.offset_vmo * block_size;
size_t max_blocks = max_transfer_ / block_size;
zx_status_t status = ZX_OK;
while (status == ZX_OK && num_blocks > 0) {
size_t blocks = num_blocks;
if (blocks > max_blocks) {
blocks = max_blocks;
}
size_t length = blocks * block_size;
// CBW Configuration
// Need to use UMS_WRITE16 if block addresses are greater than 32 bit
if (params.total_blocks > UINT32_MAX) {
scsi_command16_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_WRITE16;
command.lba = htobe64(block_offset);
command.length = htobe32(static_cast<uint32_t>(blocks));
SendCbw(params.lun, static_cast<uint32_t>(length), USB_DIR_OUT, sizeof(command), &command);
} else if (blocks <= UINT16_MAX) {
scsi_command10_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_WRITE10;
command.lba = htobe32(static_cast<uint32_t>(block_offset));
command.length_hi = static_cast<uint8_t>(static_cast<uint32_t>(blocks) >> 8);
command.length_lo = static_cast<uint8_t>(static_cast<uint32_t>(blocks) & 0xFF);
SendCbw(params.lun, static_cast<uint32_t>(length), USB_DIR_OUT, sizeof(command), &command);
} else {
scsi_command12_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_WRITE12;
command.lba = htobe32(static_cast<uint32_t>(block_offset));
command.length = htobe32(static_cast<uint32_t>(blocks));
SendCbw(params.lun, static_cast<uint32_t>(length), USB_DIR_OUT, sizeof(command), &command);
}
status = DataTransfer(txn, vmo_offset, length, bulk_out_addr_);
block_offset += blocks;
num_blocks -= static_cast<uint32_t>(blocks);
vmo_offset += (blocks * block_size);
// receive CSW
uint32_t residue;
status = ReadCsw(&residue);
if (status == ZX_OK && residue) {
zxlogf(ERROR, "unexpected residue in Write");
status = ZX_ERR_IO;
}
}
return status;
}
zx_status_t UsbMassStorageDevice::AddBlockDevice(fbl::RefPtr<UmsBlockDevice> dev) {
BlockDeviceParameters params = dev->GetBlockDeviceParameters();
uint8_t lun = params.lun;
scsi_read_capacity_10_t data;
zx_status_t status = ReadCapacity(lun, &data);
if (status < 0) {
zxlogf(ERROR, "read_capacity10 failed: %d", status);
return status;
}
params.total_blocks = betoh32(data.lba);
params.block_size = betoh32(data.block_length);
if (params.total_blocks == 0xFFFFFFFF) {
scsi_read_capacity_16_t data;
status = ReadCapacity(lun, &data);
if (status < 0) {
zxlogf(ERROR, "read_capacity16 failed: %d", status);
return status;
}
params.total_blocks = betoh64(data.lba);
params.block_size = betoh32(data.block_length);
}
if (params.block_size == 0) {
zxlogf(ERROR, "UMS zero block size");
return ZX_ERR_INVALID_ARGS;
}
// +1 because this returns the address of the final block, and blocks are zero indexed
params.total_blocks++;
params.max_transfer = static_cast<uint32_t>(max_transfer_);
dev->SetBlockDeviceParameters(params);
// determine if LUN is read-only
scsi_mode_sense_6_data_t ms_data;
status = ModeSense(lun, &ms_data);
if (status != ZX_OK) {
zxlogf(ERROR, "ModeSense failed: %d", status);
return status;
}
unsigned char cache_sense[20];
status = ModeSense(lun, 0x08, cache_sense, sizeof(cache_sense));
params = dev->GetBlockDeviceParameters();
if (status != ZX_OK) {
zxlogf(WARNING, "CacheSense failed: %d", status);
params.cache_enabled = true;
} else {
params.cache_enabled = cache_sense[6] & (1 << 2);
}
if (ms_data.device_specific_param & MODE_SENSE_DSP_RO) {
params.flags |= BLOCK_FLAG_READONLY;
} else {
params.flags &= ~BLOCK_FLAG_READONLY;
}
DEBUG_PRINT(("UMS: block size is: 0x%08x\n", params.block_size));
DEBUG_PRINT(("UMS: total blocks is: %" PRId64 "\n", params.total_blocks));
DEBUG_PRINT(("UMS: total size is: %" PRId64 "\n", params.total_blocks * params.block_size));
DEBUG_PRINT(("UMS: read-only: %d removable: %d\n", !!(params.flags & BLOCK_FLAG_READONLY),
!!(params.flags & BLOCK_FLAG_REMOVABLE)));
dev->SetBlockDeviceParameters(params);
return dev->Add();
}
zx_status_t UsbMassStorageDevice::CheckLunsReady() {
zx_status_t status = ZX_OK;
for (uint8_t lun = 0; lun <= max_lun_ && status == ZX_OK; lun++) {
auto dev = block_devs_[lun];
bool ready = false;
status = TestUnitReady(lun);
if (status == ZX_OK) {
ready = true;
}
if (status == ZX_ERR_BAD_STATE) {
ready = false;
// command returned CSW_FAILED. device is there but media is not ready.
uint8_t request_sense_data[UMS_REQUEST_SENSE_TRANSFER_LENGTH];
status = RequestSense(lun, request_sense_data);
}
if (status != ZX_OK) {
break;
}
BlockDeviceParameters params = dev->GetBlockDeviceParameters();
if (ready && !params.device_added) {
// this will set UmsBlockDevice.device_added if it succeeds
status = AddBlockDevice(dev);
params = dev->GetBlockDeviceParameters();
if (status == ZX_OK) {
params.device_added = true;
} else {
zxlogf(ERROR, "UMS: device_add for block device failed %d", status);
}
} else if (!ready && params.device_added) {
dev->DdkAsyncRemove();
params = dev->GetBlockDeviceParameters();
params.device_added = false;
}
dev->SetBlockDeviceParameters(params);
}
return status;
}
int UsbMassStorageDevice::WorkerThread(ddk::InitTxn&& init_txn) {
zx_status_t status = ZX_OK;
for (uint8_t lun = 0; lun <= max_lun_; lun++) {
uint8_t inquiry_data[UMS_INQUIRY_TRANSFER_LENGTH];
status = Inquiry(lun, inquiry_data);
if (status < 0) {
zxlogf(ERROR, "Inquiry failed for lun %d status: %d", lun, status);
init_txn.Reply(status);
return 0;
}
uint8_t rmb = inquiry_data[1] & 0x80; // Removable Media Bit
if (rmb) {
BlockDeviceParameters params = block_devs_[lun]->GetBlockDeviceParameters();
params.flags |= BLOCK_FLAG_REMOVABLE;
block_devs_[lun]->SetBlockDeviceParameters(params);
}
}
init_txn.Reply(ZX_OK);
bool wait = true;
if (CheckLunsReady() != ZX_OK) {
return status;
}
ums::Transaction* current_txn = nullptr;
while (1) {
if (wait) {
status = waiter_->Wait(&txn_completion_, ZX_SEC(1));
if (list_is_empty(&queued_txns_) && !dead_) {
if (CheckLunsReady() != ZX_OK) {
return status;
}
continue;
}
sync_completion_reset(&txn_completion_);
}
Transaction* txn = nullptr;
{
fbl::AutoLock l(&txn_lock_);
if (dead_) {
break;
}
txn = list_remove_head_type(&queued_txns_, Transaction, node);
if (txn == NULL) {
wait = true;
continue;
} else {
wait = false;
}
current_txn = txn;
}
zxlogf(DEBUG, "UMS PROCESS (%p)", &txn->op);
UmsBlockDevice* dev = txn->dev;
const auto& params = dev->GetBlockDeviceParameters();
zx_status_t status;
switch (txn->op.command & BLOCK_OP_MASK) {
case BLOCK_OP_READ:
if ((status = Read(dev, txn)) != ZX_OK) {
zxlogf(ERROR, "ums: read of %u @ %zu failed: %d", txn->op.rw.length,
txn->op.rw.offset_dev, status);
}
break;
case BLOCK_OP_WRITE:
if ((status = Write(dev, txn)) != ZX_OK) {
zxlogf(ERROR, "ums: write of %u @ %zu failed: %d", txn->op.rw.length,
txn->op.rw.offset_dev, status);
}
break;
case BLOCK_OP_FLUSH:
if (params.cache_enabled) {
scsi_command10_t command;
memset(&command, 0, sizeof(command));
command.opcode = UMS_SYNCHRONIZE_CACHE;
command.misc = 0;
const auto& params = dev->GetBlockDeviceParameters();
SendCbw(params.lun, 0, USB_DIR_OUT, sizeof(command), &command);
uint32_t residue;
status = ReadCsw(&residue);
if (status == ZX_OK && residue) {
zxlogf(ERROR, "unexpected residue in Write");
status = ZX_ERR_IO;
}
} else {
status = ZX_OK;
}
break;
default:
status = ZX_ERR_INVALID_ARGS;
break;
}
{
fbl::AutoLock l(&txn_lock_);
if (current_txn == txn) {
txn->Complete(status);
current_txn = nullptr;
}
}
}
// complete any pending txns
list_node_t txns = LIST_INITIAL_VALUE(txns);
{
fbl::AutoLock l(&txn_lock_);
list_move(&queued_txns_, &txns);
}
Transaction* txn;
while ((txn = list_remove_head_type(&queued_txns_, Transaction, node)) != NULL) {
switch (txn->op.command & BLOCK_OP_MASK) {
case BLOCK_OP_READ:
zxlogf(ERROR, "ums: read of %u @ %zu discarded during unbind", txn->op.rw.length,
txn->op.rw.offset_dev);
break;
case BLOCK_OP_WRITE:
zxlogf(ERROR, "ums: write of %u @ %zu discarded during unbind", txn->op.rw.length,
txn->op.rw.offset_dev);
break;
}
txn->Complete(ZX_ERR_IO_NOT_PRESENT);
}
return ZX_OK;
}
static zx_status_t bind(void* ctx, zx_device_t* parent) {
fbl::AllocChecker checker;
UsbMassStorageDevice* device(new (&checker)
UsbMassStorageDevice(fbl::MakeRefCounted<WaiterImpl>(), parent));
if (!checker.check()) {
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = device->Init(false /* is_test_mode */);
return status;
}
static constexpr zx_driver_ops_t usb_mass_storage_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = bind;
return ops;
}();
} // namespace ums
// clang-format off
ZIRCON_DRIVER_BEGIN(usb_mass_storage, ums::usb_mass_storage_driver_ops, "zircon", "0.1", 4)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB_INTERFACE),
BI_ABORT_IF(NE, BIND_USB_CLASS, USB_CLASS_MSC),
BI_ABORT_IF(NE, BIND_USB_SUBCLASS, USB_SUBCLASS_MSC_SCSI),
BI_MATCH_IF(EQ, BIND_USB_PROTOCOL, USB_PROTOCOL_MSC_BULK_ONLY),
ZIRCON_DRIVER_END(usb_mass_storage)
// clang-format on