* Copyright 2008-2023, Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Michael Lotz <mmlr@mlotz.ch>
* Augustin Cavalier <waddlesplash>
*/
#include "usb_disk.h"
#include <ByteOrder.h>
#include <StackOrHeapArray.h>
#include <Drivers.h>
#include <bus/USB.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <kernel.h>
#include <fs/devfs.h>
#include <syscall_restart.h>
#include <util/AutoLock.h>
#include "IOSchedulerSimple.h"
#include "scsi_sense.h"
#include "usb_disk_scsi.h"
#include "icons.h"
#define MAX_IO_BLOCKS (256)
#define USB_DISK_DEVICE_MODULE_NAME "drivers/disk/usb_disk/device_v1"
#define USB_DISK_DRIVER_MODULE_NAME "drivers/disk/usb_disk/driver_v1"
#define USB_DISK_DEVICE_ID_GENERATOR "usb_disk/device_id"
#define DRIVER_NAME "usb_disk"
#define DEVICE_NAME_BASE "disk/usb/"
#define DEVICE_NAME DEVICE_NAME_BASE "%" B_PRIu32 "/%d/raw"
#ifdef TRACE_USB_DISK
#define TRACE(x...) dprintf(DRIVER_NAME ": " x)
#define TRACE_ALWAYS(x...) dprintf(DRIVER_NAME ": " x)
#define CALLED() TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
#else
#define TRACE(x...)
#define CALLED()
#define TRACE_ALWAYS(x...) dprintf(DRIVER_NAME ": " x)
#endif
device_manager_info *gDeviceManager;
static usb_module_info *gUSBModule = NULL;
struct {
const char *vendor;
const char *product;
device_icon *icon;
const char *name;
} kIconMatches[] = {
{ NULL, " SD Reader", &kSDIconData, "devices/drive-removable-media-flash" },
{ NULL, " MS Reader", &kMSIconData, "devices/drive-removable-media-flash" },
{ "MEDIATEK", NULL, &kMobileIconData,
"devices/drive-removable-media-flash" },
{ NULL, NULL, NULL, NULL }
};
static void usb_disk_callback(void *cookie, status_t status, void *data,
size_t actualLength);
static status_t usb_disk_do_io(void* cookie, IOOperation* operation);
uint8 usb_disk_get_max_lun(disk_device *device);
void usb_disk_reset_recovery(disk_device *device);
status_t usb_disk_receive_csw(disk_device *device,
usb_massbulk_command_status_wrapper *status);
status_t usb_disk_send_diagnostic(device_lun *lun);
status_t usb_disk_request_sense(device_lun *lun, err_act *action);
status_t usb_disk_mode_sense(device_lun *lun);
status_t usb_disk_test_unit_ready(device_lun *lun, err_act *action = NULL);
status_t usb_disk_inquiry(device_lun *lun);
status_t usb_disk_reset_capacity(device_lun *lun);
status_t usb_disk_update_capacity(device_lun *lun);
status_t usb_disk_synchronize(device_lun *lun, bool force);
disk_device_s::disk_device_s()
:
notify(-1),
interruptLock(-1)
{
recursive_lock_init(&io_lock, "usb_disk i/o lock");
mutex_init(&lock, "usb_disk device lock");
}
disk_device_s::~disk_device_s()
{
recursive_lock_destroy(&io_lock);
mutex_destroy(&lock);
if (notify >= 0)
delete_sem(notify);
if (interruptLock >= 0)
delete_sem(interruptLock);
}
void
usb_disk_free_device_and_luns(disk_device *device)
{
ASSERT_LOCKED_MUTEX(&device->lock);
for (uint8 i = 0; i < device->lun_count; i++) {
delete device->luns[i]->io_scheduler;
free(device->luns[i]);
}
free(device->luns);
delete device;
}
static status_t
usb_disk_mass_storage_reset(disk_device *device)
{
return gUSBModule->send_request(device->device, USB_REQTYPE_INTERFACE_OUT
| USB_REQTYPE_CLASS, USB_MASSBULK_REQUEST_MASS_STORAGE_RESET, 0x0000,
device->interface, 0, NULL, NULL);
}
uint8
usb_disk_get_max_lun(disk_device *device)
{
ASSERT_LOCKED_RECURSIVE(&device->io_lock);
uint8 result = 0;
size_t actualLength = 0;
if (gUSBModule->send_request(device->device, USB_REQTYPE_INTERFACE_IN
| USB_REQTYPE_CLASS, USB_MASSBULK_REQUEST_GET_MAX_LUN, 0x0000,
device->interface, 1, &result, &actualLength) != B_OK
|| actualLength != 1) {
return 0;
}
if (result > MAX_LOGICAL_UNIT_NUMBER) {
return 0;
}
return result;
}
static void
usb_disk_clear_halt(usb_pipe pipe)
{
gUSBModule->cancel_queued_transfers(pipe);
gUSBModule->clear_feature(pipe, USB_FEATURE_ENDPOINT_HALT);
}
void
usb_disk_reset_recovery(disk_device *device, err_act *_action)
{
TRACE("reset recovery\n");
ASSERT_LOCKED_RECURSIVE(&device->io_lock);
usb_disk_mass_storage_reset(device);
usb_disk_clear_halt(device->bulk_in);
usb_disk_clear_halt(device->bulk_out);
if (device->is_ufi)
usb_disk_clear_halt(device->interrupt);
if (_action != NULL)
*_action = err_act_retry;
}
struct transfer_data {
union {
physical_entry* phys_vecs;
iovec* vecs;
};
uint32 vec_count = 0;
bool physical = false;
};
static status_t
usb_disk_transfer_data(disk_device *device, bool directionIn, const transfer_data& data)
{
status_t result;
if (data.physical) {
result = gUSBModule->queue_bulk_v_physical(
directionIn ? device->bulk_in : device->bulk_out,
data.phys_vecs, data.vec_count, usb_disk_callback, device);
} else {
result = gUSBModule->queue_bulk_v(
directionIn ? device->bulk_in : device->bulk_out,
data.vecs, data.vec_count, usb_disk_callback, device);
}
if (result != B_OK) {
TRACE_ALWAYS("failed to queue data transfer: %s\n", strerror(result));
return result;
}
mutex_unlock(&device->lock);
do {
result = acquire_sem_etc(device->notify, 1, B_RELATIVE_TIMEOUT,
10 * 1000 * 1000);
if (result == B_TIMED_OUT) {
gUSBModule->cancel_queued_transfers(directionIn ? device->bulk_in
: device->bulk_out);
acquire_sem_etc(device->notify, 1, B_RELATIVE_TIMEOUT, 0);
}
} while (result == B_INTERRUPTED);
mutex_lock(&device->lock);
if (result != B_OK) {
TRACE_ALWAYS("acquire_sem failed while waiting for data transfer: %s\n",
strerror(result));
return result;
}
return B_OK;
}
static status_t
usb_disk_transfer_data(disk_device *device, bool directionIn,
void* buffer, size_t dataLength)
{
iovec vec;
vec.iov_base = buffer;
vec.iov_len = dataLength;
struct transfer_data data;
data.vecs = &vec;
data.vec_count = 1;
return usb_disk_transfer_data(device, directionIn, data);
}
static void
callback_interrupt(void* cookie, int32 status, void* data, size_t length)
{
disk_device* device = (disk_device*)cookie;
release_sem(device->interruptLock);
if (length != 2) {
TRACE_ALWAYS("interrupt of length %" B_PRIuSIZE "! (expected 2)\n",
length);
return;
}
gUSBModule->queue_interrupt(device->interrupt, device->interruptBuffer, 2,
callback_interrupt, cookie);
}
static status_t
receive_csw_interrupt(disk_device *device,
interrupt_status_wrapper *status)
{
TRACE("Waiting for result...\n");
gUSBModule->queue_interrupt(device->interrupt,
device->interruptBuffer, 2, callback_interrupt, device);
acquire_sem(device->interruptLock);
status->status = device->interruptBuffer[0];
status->misc = device->interruptBuffer[1];
return B_OK;
}
static status_t
receive_csw_bulk(disk_device *device,
usb_massbulk_command_status_wrapper *status)
{
status_t result = usb_disk_transfer_data(device, true, status,
sizeof(usb_massbulk_command_status_wrapper));
if (result != B_OK)
return result;
if (device->status != B_OK
|| device->actual_length
!= sizeof(usb_massbulk_command_status_wrapper)) {
return B_ERROR;
}
return B_OK;
}
status_t
usb_disk_operation_interrupt(device_lun *lun, uint8* operation,
const transfer_data& data, size_t *dataLength,
bool directionIn, err_act *_action)
{
TRACE("operation: lun: %u; op: 0x%x; data: %p; dlen: %p (%lu); in: %c\n",
lun->logical_unit_number, operation[0], data.vecs, dataLength,
dataLength ? *dataLength : 0, directionIn ? 'y' : 'n');
ASSERT_LOCKED_RECURSIVE(&lun->device->io_lock);
disk_device* device = lun->device;
size_t actualLength = 12;
status_t result = gUSBModule->send_request(device->device,
USB_REQTYPE_CLASS | USB_REQTYPE_INTERFACE_OUT, 0 ,
0, device->interface, 12, operation, &actualLength);
if (result != B_OK || actualLength != 12) {
TRACE("Command stage: wrote %ld bytes (error: %s)\n",
actualLength, strerror(result));
if (operation[0] != SCSI_REQUEST_SENSE_6) {
usb_disk_request_sense(lun, _action);
}
return result;
}
size_t transferedData = 0;
if (data.vec_count != 0) {
result = usb_disk_transfer_data(device, directionIn, data);
if (result != B_OK) {
TRACE("Error %s in data phase\n", strerror(result));
return result;
}
transferedData = device->actual_length;
if (device->status != B_OK || transferedData != *dataLength) {
if (device->status == B_DEV_STALLED) {
TRACE("stall while transfering data\n");
usb_disk_clear_halt(directionIn ? device->bulk_in : device->bulk_out);
} else {
TRACE_ALWAYS("sending or receiving of the data failed\n");
usb_disk_reset_recovery(device, _action);
return B_IO_ERROR;
}
}
}
if (operation[0] != SCSI_REQUEST_SENSE_6) {
interrupt_status_wrapper status;
result = receive_csw_interrupt(device, &status);
if (result != B_OK) {
TRACE("Error receiving interrupt: %s. Retrying...\n",
strerror(result));
usb_disk_clear_halt(device->bulk_in);
result = receive_csw_interrupt(device, &status);
}
if (result != B_OK) {
TRACE_ALWAYS("receiving the command status interrupt failed\n");
usb_disk_reset_recovery(device, _action);
return result;
}
result = usb_disk_request_sense(lun, _action);
}
return result;
}
status_t
usb_disk_operation_bulk(device_lun *lun, uint8 *operation, size_t operationLength,
const transfer_data& data, size_t *dataLength,
bool directionIn, err_act *_action)
{
TRACE("operation: lun: %u; op: %u; data: %p; dlen: %p (%lu); in: %c\n",
lun->logical_unit_number, operation[0],
data.vecs, dataLength, dataLength ? *dataLength : 0,
directionIn ? 'y' : 'n');
ASSERT_LOCKED_RECURSIVE(&lun->device->io_lock);
disk_device *device = lun->device;
usb_massbulk_command_block_wrapper command;
command.signature = USB_MASSBULK_CBW_SIGNATURE;
command.tag = device->current_tag++;
command.data_transfer_length = (dataLength != NULL ? *dataLength : 0);
command.flags = (directionIn ? USB_MASSBULK_CBW_DATA_INPUT
: USB_MASSBULK_CBW_DATA_OUTPUT);
command.lun = lun->logical_unit_number;
command.command_block_length
= device->is_atapi ? ATAPI_COMMAND_LENGTH : operationLength;
memset(command.command_block, 0, sizeof(command.command_block));
memcpy(command.command_block, operation, operationLength);
status_t result = usb_disk_transfer_data(device, false, &command,
sizeof(usb_massbulk_command_block_wrapper));
if (result != B_OK)
return result;
if (device->status != B_OK ||
device->actual_length != sizeof(usb_massbulk_command_block_wrapper)) {
TRACE_ALWAYS("sending the command block wrapper failed: %s\n",
strerror(device->status));
usb_disk_reset_recovery(device, _action);
return B_IO_ERROR;
}
size_t transferedData = 0;
if (data.vec_count != 0) {
result = usb_disk_transfer_data(device, directionIn, data);
if (result != B_OK)
return result;
transferedData = device->actual_length;
if (device->status != B_OK || transferedData != *dataLength) {
if (device->status == B_DEV_STALLED) {
TRACE("stall while transfering data\n");
usb_disk_clear_halt(directionIn ? device->bulk_in : device->bulk_out);
} else {
TRACE_ALWAYS("sending or receiving of the data failed: %s\n",
strerror(device->status));
usb_disk_reset_recovery(device, _action);
return B_IO_ERROR;
}
}
}
usb_massbulk_command_status_wrapper status;
result = receive_csw_bulk(device, &status);
if (result != B_OK) {
usb_disk_clear_halt(device->bulk_in);
result = receive_csw_bulk(device, &status);
}
if (result != B_OK) {
TRACE_ALWAYS("receiving the command status wrapper failed: %s\n",
strerror(result));
usb_disk_reset_recovery(device, _action);
return result;
}
if (status.signature != USB_MASSBULK_CSW_SIGNATURE
|| status.tag != command.tag) {
TRACE_ALWAYS("command status wrapper is not valid: %#" B_PRIx32 "\n",
status.signature);
usb_disk_reset_recovery(device, _action);
return B_ERROR;
}
switch (status.status) {
case USB_MASSBULK_CSW_STATUS_COMMAND_PASSED:
case USB_MASSBULK_CSW_STATUS_COMMAND_FAILED:
{
uint32 residue = command.data_transfer_length - transferedData;
if (dataLength != NULL) {
*dataLength -= residue;
if (transferedData < *dataLength) {
TRACE_ALWAYS("less data transfered than indicated: %"
B_PRIuSIZE " vs. %" B_PRIuSIZE "\n", transferedData,
*dataLength);
*dataLength = transferedData;
}
}
if (status.status == USB_MASSBULK_CSW_STATUS_COMMAND_PASSED) {
return B_OK;
} else {
if (operation[0] == SCSI_REQUEST_SENSE_6)
return B_ERROR;
if (operation[0] != SCSI_TEST_UNIT_READY_6) {
TRACE_ALWAYS("operation %#" B_PRIx8
" failed at the SCSI level\n", operation[0]);
}
result = usb_disk_request_sense(lun, _action);
return result == B_OK ? B_ERROR : result;
}
}
case USB_MASSBULK_CSW_STATUS_PHASE_ERROR:
{
TRACE_ALWAYS("phase error in operation %#" B_PRIx8 "\n",
operation[0]);
usb_disk_reset_recovery(device, _action);
return B_ERROR;
}
default:
{
TRACE_ALWAYS("command status wrapper has invalid status\n");
usb_disk_reset_recovery(device, _action);
return B_ERROR;
}
}
}
static status_t
usb_disk_operation(device_lun *lun, uint8* operation, size_t opLength,
const transfer_data& data, size_t *dataLength,
bool directionIn, err_act *_action = NULL)
{
if (lun->device->is_ufi) {
return usb_disk_operation_interrupt(lun, operation,
data, dataLength, directionIn, _action);
} else {
return usb_disk_operation_bulk(lun, operation, opLength,
data, dataLength, directionIn, _action);
}
}
static status_t
usb_disk_operation(device_lun *lun, uint8* operation, size_t opLength,
void *buffer, size_t *dataLength,
bool directionIn, err_act *_action = NULL)
{
iovec vec;
vec.iov_base = buffer;
struct transfer_data data;
data.vecs = &vec;
if (dataLength != NULL && *dataLength != 0) {
vec.iov_len = *dataLength;
data.vec_count = 1;
} else {
vec.iov_len = 0;
data.vec_count = 0;
}
return usb_disk_operation(lun, operation, opLength,
data, dataLength, directionIn, _action);
}
status_t
usb_disk_send_diagnostic(device_lun *lun)
{
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_SEND_DIAGNOSTIC;
commandBlock[1] = (lun->logical_unit_number << 5) | 4;
status_t result = usb_disk_operation(lun, commandBlock, 6, NULL, NULL, false);
int retry = 100;
err_act action = err_act_ok;
while (result == B_DEV_NO_MEDIA && retry > 0) {
snooze(10000);
result = usb_disk_request_sense(lun, &action);
retry--;
}
if (result != B_OK)
TRACE("Send Diagnostic failed: %s\n", strerror(result));
return result;
}
status_t
usb_disk_request_sense(device_lun *lun, err_act *_action)
{
size_t dataLength = sizeof(scsi_request_sense_6_parameter);
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_REQUEST_SENSE_6;
commandBlock[1] = lun->logical_unit_number << 5;
commandBlock[2] = 0;
commandBlock[4] = dataLength;
scsi_request_sense_6_parameter parameter;
status_t result = B_ERROR;
for (uint32 tries = 0; tries < 3; tries++) {
result = usb_disk_operation(lun, commandBlock, 6, ¶meter,
&dataLength, true);
if (result != B_TIMED_OUT)
break;
snooze(100000);
}
if (result != B_OK) {
TRACE_ALWAYS("getting request sense data failed: %s\n",
strerror(result));
return result;
}
const char *label = NULL;
err_act action = err_act_fail;
status_t status = B_ERROR;
scsi_get_sense_asc_info((parameter.additional_sense_code << 8)
| parameter.additional_sense_code_qualifier, &label, &action,
&status);
if (parameter.sense_key > SCSI_SENSE_KEY_NOT_READY
&& parameter.sense_key != SCSI_SENSE_KEY_UNIT_ATTENTION) {
TRACE_ALWAYS("request_sense: key: 0x%02x; asc: 0x%02x; ascq: "
"0x%02x; %s\n", parameter.sense_key,
parameter.additional_sense_code,
parameter.additional_sense_code_qualifier,
label ? label : "(unknown)");
}
if ((parameter.additional_sense_code == 0
&& parameter.additional_sense_code_qualifier == 0)
|| label == NULL) {
scsi_get_sense_key_info(parameter.sense_key, &label, &action, &status);
}
if (status == B_DEV_MEDIA_CHANGED) {
lun->media_changed = true;
lun->media_present = true;
} else if (parameter.sense_key == SCSI_SENSE_KEY_UNIT_ATTENTION
&& status != B_DEV_NO_MEDIA) {
lun->media_present = true;
} else if (status == B_DEV_NOT_READY || status == B_DEV_NO_MEDIA) {
lun->media_present = false;
usb_disk_reset_capacity(lun);
}
if (_action != NULL)
*_action = action;
return status;
}
status_t
usb_disk_mode_sense(device_lun *lun)
{
size_t dataLength = sizeof(scsi_mode_sense_6_parameter);
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_MODE_SENSE_6;
commandBlock[1] = SCSI_MODE_PAGE_DEVICE_CONFIGURATION;
commandBlock[2] = 0;
commandBlock[3] = dataLength >> 8;
commandBlock[4] = dataLength;
scsi_mode_sense_6_parameter parameter;
status_t result = usb_disk_operation(lun, commandBlock, 6,
¶meter, &dataLength, true);
if (result != B_OK) {
TRACE_ALWAYS("getting mode sense data failed: %s\n", strerror(result));
return result;
}
lun->write_protected
= (parameter.device_specific & SCSI_DEVICE_SPECIFIC_WRITE_PROTECT)
!= 0;
TRACE_ALWAYS("write protected: %s\n", lun->write_protected ? "yes" : "no");
return B_OK;
}
status_t
usb_disk_test_unit_ready(device_lun *lun, err_act *_action)
{
if (lun->device->is_ufi || !lun->device->tur_supported)
return B_OK;
status_t result = B_OK;
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
if (lun->device->is_atapi) {
commandBlock[0] = SCSI_START_STOP_UNIT_6;
commandBlock[1] = lun->logical_unit_number << 5;
commandBlock[2] = 0;
commandBlock[3] = 0;
commandBlock[4] = 1;
result = usb_disk_operation(lun, commandBlock, 6, NULL, NULL, false,
_action);
} else {
commandBlock[0] = SCSI_TEST_UNIT_READY_6;
commandBlock[1] = lun->logical_unit_number << 5;
commandBlock[2] = 0;
commandBlock[3] = 0;
commandBlock[4] = 0;
result = usb_disk_operation(lun, commandBlock, 6, NULL, NULL, true,
_action);
}
if (result == B_DEV_INVALID_IOCTL) {
lun->device->tur_supported = false;
return B_OK;
}
return result;
}
status_t
usb_disk_inquiry(device_lun *lun)
{
size_t dataLength = sizeof(scsi_inquiry_6_parameter);
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_INQUIRY_6;
commandBlock[1] = lun->logical_unit_number << 5;
commandBlock[2] = 0;
commandBlock[4] = dataLength;
scsi_inquiry_6_parameter parameter;
status_t result = B_ERROR;
err_act action = err_act_ok;
for (uint32 tries = 0; tries < 3; tries++) {
result = usb_disk_operation(lun, commandBlock, 6, ¶meter,
&dataLength, true, &action);
if (result == B_OK || (action != err_act_retry
&& action != err_act_many_retries)) {
break;
}
}
if (result != B_OK) {
TRACE_ALWAYS("getting inquiry data failed: %s\n", strerror(result));
lun->device_type = B_DISK;
lun->removable = true;
return result;
}
TRACE("peripherial_device_type 0x%02x\n",
parameter.peripherial_device_type);
TRACE("peripherial_qualifier 0x%02x\n",
parameter.peripherial_qualifier);
TRACE("removable_medium %s\n",
parameter.removable_medium ? "yes" : "no");
TRACE("version 0x%02x\n", parameter.version);
TRACE("response_data_format 0x%02x\n", parameter.response_data_format);
TRACE_ALWAYS("vendor_identification \"%.8s\"\n",
parameter.vendor_identification);
TRACE_ALWAYS("product_identification \"%.16s\"\n",
parameter.product_identification);
TRACE_ALWAYS("product_revision_level \"%.4s\"\n",
parameter.product_revision_level);
memcpy(lun->vendor_name, parameter.vendor_identification,
MIN(sizeof(lun->vendor_name), sizeof(parameter.vendor_identification)));
memcpy(lun->product_name, parameter.product_identification,
MIN(sizeof(lun->product_name),
sizeof(parameter.product_identification)));
memcpy(lun->product_revision, parameter.product_revision_level,
MIN(sizeof(lun->product_revision),
sizeof(parameter.product_revision_level)));
lun->device_type = parameter.peripherial_device_type;
lun->removable = (parameter.removable_medium == 1);
return B_OK;
}
status_t
usb_disk_reset_capacity(device_lun *lun)
{
lun->block_size = 512;
lun->block_count = 0;
return B_OK;
}
static status_t
usb_disk_update_capacity_16(device_lun *lun)
{
size_t dataLength = sizeof(scsi_read_capacity_16_parameter);
scsi_read_capacity_16_parameter parameter;
status_t result = B_ERROR;
err_act action = err_act_ok;
uint8 commandBlock[16];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_SERVICE_ACTION_IN;
commandBlock[1] = SCSI_SAI_READ_CAPACITY_16;
commandBlock[10] = dataLength >> 24;
commandBlock[11] = dataLength >> 16;
commandBlock[12] = dataLength >> 8;
commandBlock[13] = dataLength;
for (int32 i = 0; i < 5; i++) {
result = usb_disk_operation(lun, commandBlock, 16, ¶meter,
&dataLength, true, &action);
if (result == B_OK || (action != err_act_retry
&& action != err_act_many_retries)) {
break;
}
}
if (result != B_OK) {
TRACE_ALWAYS("failed to update capacity: %s\n", strerror(result));
lun->media_present = false;
lun->media_changed = false;
usb_disk_reset_capacity(lun);
return result;
}
lun->media_present = true;
lun->media_changed = false;
lun->block_size = B_BENDIAN_TO_HOST_INT32(parameter.logical_block_length);
lun->physical_block_size = lun->block_size;
lun->block_count =
B_BENDIAN_TO_HOST_INT64(parameter.last_logical_block_address) + 1;
return B_OK;
}
status_t
usb_disk_update_capacity(device_lun *lun)
{
size_t dataLength = sizeof(scsi_read_capacity_10_parameter);
scsi_read_capacity_10_parameter parameter;
status_t result = B_ERROR;
err_act action = err_act_ok;
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_READ_CAPACITY_10;
commandBlock[1] = lun->logical_unit_number << 5;
for (int32 i = 0; i < 5; i++) {
result = usb_disk_operation(lun, commandBlock, 10, ¶meter,
&dataLength, true, &action);
if (result == B_OK || (action != err_act_retry
&& action != err_act_many_retries)) {
break;
}
if (lun->device->is_ufi && (result == B_DEV_NO_MEDIA
|| result == B_TIMED_OUT || result == B_DEV_STALLED))
snooze(10000);
}
if (result != B_OK) {
TRACE_ALWAYS("failed to update capacity: %s\n", strerror(result));
lun->media_present = false;
lun->media_changed = false;
usb_disk_reset_capacity(lun);
return result;
}
lun->media_present = true;
lun->media_changed = false;
lun->block_size = B_BENDIAN_TO_HOST_INT32(parameter.logical_block_length);
lun->physical_block_size = lun->block_size;
lun->block_count =
B_BENDIAN_TO_HOST_INT32(parameter.last_logical_block_address) + 1;
if (lun->block_count == 0) {
result = usb_disk_update_capacity_16(lun);
if (result != B_OK)
return result;
}
if (lun->io_scheduler != NULL
&& lun->io_scheduler->GetDMAResource()->BlockSize() != lun->block_size) {
IOScheduler* oldScheduler = lun->io_scheduler;
lun->io_scheduler = NULL;
mutex_unlock(&lun->device->lock);
recursive_lock_unlock(&lun->device->io_lock);
delete oldScheduler;
recursive_lock_lock(&lun->device->io_lock);
mutex_lock(&lun->device->lock);
}
if (lun->io_scheduler == NULL) {
dma_restrictions restrictions = {};
restrictions.high_address = UINT32_MAX;
restrictions.max_transfer_size = (lun->block_size * MAX_IO_BLOCKS);
DMAResource* dmaResource = new DMAResource;
result = dmaResource->Init(restrictions, lun->block_size, 1, 1);
if (result != B_OK)
return result;
lun->io_scheduler = new IOSchedulerSimple(dmaResource);
result = lun->io_scheduler->Init("usb_disk");
if (result != B_OK)
panic("initializing IOScheduler failed: %s", strerror(result));
lun->io_scheduler->SetCallback(usb_disk_do_io, lun);
}
return B_OK;
}
status_t
usb_disk_synchronize(device_lun *lun, bool force)
{
if (lun->device->is_ufi) {
return B_UNSUPPORTED;
}
if (lun->device->sync_support == 0) {
return B_UNSUPPORTED;
}
if (!lun->should_sync && !force)
return B_OK;
if (!lun->media_present)
return B_DEV_NO_MEDIA;
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_SYNCHRONIZE_CACHE_10;
commandBlock[1] = lun->logical_unit_number << 5;
status_t result = usb_disk_operation(lun, commandBlock, 10,
NULL, NULL, false);
if (result == B_OK) {
lun->device->sync_support = SYNC_SUPPORT_RELOAD;
lun->should_sync = false;
return B_OK;
}
if (result == B_DEV_INVALID_IOCTL)
lun->device->sync_support = 0;
else
lun->device->sync_support--;
return result;
}
static void
usb_disk_callback(void *cookie, status_t status, void *data,
size_t actualLength)
{
disk_device *device = (disk_device *)cookie;
device->status = status;
device->actual_length = actualLength;
release_sem(device->notify);
}
static status_t
usb_disk_attach(device_node *node, usb_device newDevice, void **cookie)
{
TRACE("device_added(0x%08" B_PRIx32 ")\n", newDevice);
disk_device *device = new(std::nothrow) disk_device;
recursive_lock_lock(&device->io_lock);
mutex_lock(&device->lock);
device->node = node;
device->device = newDevice;
device->removed = false;
device->open_count = 0;
device->interface = 0xff;
device->current_tag = 0;
device->sync_support = SYNC_SUPPORT_RELOAD;
device->tur_supported = true;
device->is_atapi = false;
device->is_ufi = false;
device->luns = NULL;
const usb_configuration_info *configuration
= gUSBModule->get_configuration(newDevice);
if (configuration == NULL) {
delete device;
return B_ERROR;
}
for (size_t i = 0; i < configuration->interface_count; i++) {
usb_interface_info *interface = configuration->interface[i].active;
if (interface == NULL)
continue;
if (interface->descr->interface_class == USB_MASS_STORAGE_DEVICE_CLASS
&& (((interface->descr->interface_subclass == 0x06
|| interface->descr->interface_subclass == 0x02
|| interface->descr->interface_subclass == 0x05 )
&& interface->descr->interface_protocol == 0x50 )
|| (interface->descr->interface_subclass == 0x04
&& interface->descr->interface_protocol == 0x00))) {
bool hasIn = false;
bool hasOut = false;
bool hasInt = false;
for (size_t j = 0; j < interface->endpoint_count; j++) {
usb_endpoint_info *endpoint = &interface->endpoint[j];
if (endpoint == NULL)
continue;
if (!hasIn && (endpoint->descr->endpoint_address
& USB_ENDPOINT_ADDR_DIR_IN) != 0
&& endpoint->descr->attributes == USB_ENDPOINT_ATTR_BULK) {
device->bulk_in = endpoint->handle;
hasIn = true;
} else if (!hasOut && (endpoint->descr->endpoint_address
& USB_ENDPOINT_ADDR_DIR_IN) == 0
&& endpoint->descr->attributes == USB_ENDPOINT_ATTR_BULK) {
device->bulk_out = endpoint->handle;
hasOut = true;
} else if (!hasInt && (endpoint->descr->endpoint_address
& USB_ENDPOINT_ADDR_DIR_IN)
&& endpoint->descr->attributes
== USB_ENDPOINT_ATTR_INTERRUPT) {
device->interrupt = endpoint->handle;
hasInt = true;
}
if (hasIn && hasOut && hasInt)
break;
}
if (!(hasIn && hasOut)) {
continue;
}
device->interface = interface->descr->interface_number;
device->is_atapi = interface->descr->interface_subclass != 0x06
&& interface->descr->interface_subclass != 0x04;
device->is_ufi = interface->descr->interface_subclass == 0x04;
if (device->is_ufi && !hasInt) {
continue;
}
break;
}
}
if (device->interface == 0xff) {
TRACE_ALWAYS("no valid bulk-only or CBI interface found\n");
delete device;
return B_ERROR;
}
device->notify = create_sem(0, "usb_disk callback notify");
if (device->notify < B_OK) {
status_t result = device->notify;
delete device;
return result;
}
if (device->is_ufi) {
device->interruptLock = create_sem(0, "usb_disk interrupt lock");
if (device->interruptLock < B_OK) {
status_t result = device->interruptLock;
delete device;
return result;
}
}
device->lun_count = usb_disk_get_max_lun(device) + 1;
device->luns = (device_lun **)malloc(device->lun_count
* sizeof(device_lun *));
for (uint8 i = 0; i < device->lun_count; i++)
device->luns[i] = NULL;
status_t result = B_OK;
TRACE_ALWAYS("device reports a lun count of %d\n", device->lun_count);
for (uint8 i = 0; i < device->lun_count; i++) {
device_lun *lun = (device_lun *)malloc(sizeof(device_lun));
if (lun == NULL) {
result = B_NO_MEMORY;
break;
}
device->luns[i] = lun;
lun->device = device;
lun->logical_unit_number = i;
lun->should_sync = false;
lun->media_present = true;
lun->media_changed = true;
lun->io_scheduler = NULL;
memset(lun->vendor_name, 0, sizeof(lun->vendor_name));
memset(lun->product_name, 0, sizeof(lun->product_name));
memset(lun->product_revision, 0, sizeof(lun->product_revision));
usb_disk_reset_capacity(lun);
result = usb_disk_inquiry(lun);
if (device->is_ufi) {
result = usb_disk_send_diagnostic(lun);
}
err_act action = err_act_ok;
for (uint32 tries = 0; tries < 8; tries++) {
TRACE("usb lun %" B_PRIu8 " inquiry attempt %" B_PRIu32 " begin\n",
i, tries);
status_t ready = usb_disk_test_unit_ready(lun, &action);
if (ready == B_OK || ready == B_DEV_NO_MEDIA
|| ready == B_DEV_MEDIA_CHANGED) {
if (lun->device_type == B_CD)
lun->write_protected = true;
else if (true)
lun->write_protected = false;
TRACE("usb lun %" B_PRIu8 " ready. write protected = %c%s\n", i,
lun->write_protected ? 'y' : 'n',
ready == B_DEV_NO_MEDIA ? " (no media inserted)" : "");
break;
}
TRACE("usb lun %" B_PRIu8 " inquiry attempt %" B_PRIu32 " failed\n",
i, tries);
if (action != err_act_retry && action != err_act_many_retries)
break;
bigtime_t snoozeTime = 1000000 * tries;
TRACE("snoozing %" B_PRIu64 " microseconds for usb lun\n",
snoozeTime);
snooze(snoozeTime);
}
if (result != B_OK)
break;
}
if (result != B_OK) {
TRACE_ALWAYS("failed to initialize logical units: %s\n",
strerror(result));
if (device->is_ufi)
gUSBModule->cancel_queued_transfers(device->interrupt);
usb_disk_free_device_and_luns(device);
return result;
}
mutex_unlock(&device->lock);
recursive_lock_unlock(&device->io_lock);
TRACE("new device: 0x%p\n", device);
*cookie = (void *)device;
return B_OK;
}
static void
usb_disk_device_removed(void *cookie)
{
TRACE("device_removed(0x%p)\n", cookie);
disk_device *device = (disk_device *)cookie;
mutex_lock(&device->lock);
for (uint8 i = 0; i < device->lun_count; i++) {
mutex_unlock(&device->lock);
gDeviceManager->unpublish_device(device->node, device->luns[i]->name);
mutex_lock(&device->lock);
}
device->removed = true;
gUSBModule->cancel_queued_transfers(device->bulk_in);
gUSBModule->cancel_queued_transfers(device->bulk_out);
if (device->is_ufi)
gUSBModule->cancel_queued_transfers(device->interrupt);
if (device->open_count == 0)
usb_disk_free_device_and_luns(device);
else
mutex_unlock(&device->lock);
}
static status_t
usb_disk_block_read(device_lun *lun, uint64 blockPosition, size_t blockCount,
struct transfer_data data, size_t *length)
{
if (!lun->media_present)
return B_DEV_NO_MEDIA;
uint8 commandBlock[16];
memset(commandBlock, 0, sizeof(commandBlock));
if (lun->device->is_ufi) {
commandBlock[0] = SCSI_READ_12;
commandBlock[1] = lun->logical_unit_number << 5;
commandBlock[2] = blockPosition >> 24;
commandBlock[3] = blockPosition >> 16;
commandBlock[4] = blockPosition >> 8;
commandBlock[5] = blockPosition;
commandBlock[6] = blockCount >> 24;
commandBlock[7] = blockCount >> 16;
commandBlock[8] = blockCount >> 8;
commandBlock[9] = blockCount;
status_t result = B_OK;
for (int tries = 0; tries < 5; tries++) {
result = usb_disk_operation(lun, commandBlock, 12, data,
length, true);
if (result == B_OK)
break;
else
snooze(10000);
}
return result;
} else if (blockPosition + blockCount < 0x100000000LL && blockCount <= 0x10000) {
commandBlock[0] = SCSI_READ_10;
commandBlock[1] = 0;
commandBlock[2] = blockPosition >> 24;
commandBlock[3] = blockPosition >> 16;
commandBlock[4] = blockPosition >> 8;
commandBlock[5] = blockPosition;
commandBlock[7] = blockCount >> 8;
commandBlock[8] = blockCount;
status_t result = usb_disk_operation(lun, commandBlock, 10,
data, length, true);
return result;
} else {
commandBlock[0] = SCSI_READ_16;
commandBlock[1] = 0;
commandBlock[2] = blockPosition >> 56;
commandBlock[3] = blockPosition >> 48;
commandBlock[4] = blockPosition >> 40;
commandBlock[5] = blockPosition >> 32;
commandBlock[6] = blockPosition >> 24;
commandBlock[7] = blockPosition >> 16;
commandBlock[8] = blockPosition >> 8;
commandBlock[9] = blockPosition;
commandBlock[10] = blockCount >> 24;
commandBlock[11] = blockCount >> 16;
commandBlock[12] = blockCount >> 8;
commandBlock[13] = blockCount;
status_t result = usb_disk_operation(lun, commandBlock, 16,
data, length, true);
return result;
}
}
static status_t
usb_disk_block_write(device_lun *lun, uint64 blockPosition, size_t blockCount,
struct transfer_data data, size_t *length)
{
if (!lun->media_present)
return B_DEV_NO_MEDIA;
uint8 commandBlock[16];
memset(commandBlock, 0, sizeof(commandBlock));
if (lun->device->is_ufi) {
commandBlock[0] = SCSI_WRITE_12;
commandBlock[1] = lun->logical_unit_number << 5;
commandBlock[2] = blockPosition >> 24;
commandBlock[3] = blockPosition >> 16;
commandBlock[4] = blockPosition >> 8;
commandBlock[5] = blockPosition;
commandBlock[6] = blockCount >> 24;
commandBlock[7] = blockCount >> 16;
commandBlock[8] = blockCount >> 8;
commandBlock[9] = blockCount;
status_t result;
result = usb_disk_operation(lun, commandBlock, 12,
data, length, false);
int retry = 10;
err_act action = err_act_ok;
while (result == B_DEV_NO_MEDIA && retry > 0) {
snooze(10000);
result = usb_disk_request_sense(lun, &action);
retry--;
}
if (result == B_OK)
lun->should_sync = true;
return result;
} else if (blockPosition + blockCount < 0x100000000LL && blockCount <= 0x10000) {
commandBlock[0] = SCSI_WRITE_10;
commandBlock[2] = blockPosition >> 24;
commandBlock[3] = blockPosition >> 16;
commandBlock[4] = blockPosition >> 8;
commandBlock[5] = blockPosition;
commandBlock[7] = blockCount >> 8;
commandBlock[8] = blockCount;
status_t result = usb_disk_operation(lun, commandBlock, 10,
data, length, false);
if (result == B_OK)
lun->should_sync = true;
return result;
} else {
commandBlock[0] = SCSI_WRITE_16;
commandBlock[1] = 0;
commandBlock[2] = blockPosition >> 56;
commandBlock[3] = blockPosition >> 48;
commandBlock[4] = blockPosition >> 40;
commandBlock[5] = blockPosition >> 32;
commandBlock[6] = blockPosition >> 24;
commandBlock[7] = blockPosition >> 16;
commandBlock[8] = blockPosition >> 8;
commandBlock[9] = blockPosition;
commandBlock[10] = blockCount >> 24;
commandBlock[11] = blockCount >> 16;
commandBlock[12] = blockCount >> 8;
commandBlock[13] = blockCount;
status_t result = usb_disk_operation(lun, commandBlock, 16,
data, length, false);
if (result == B_OK)
lun->should_sync = true;
return result;
}
}
static status_t
usb_disk_init_device(void* _info, void** _cookie)
{
CALLED();
*_cookie = _info;
return B_OK;
}
static void
usb_disk_uninit_device(void* _cookie)
{
}
static status_t
usb_disk_open(void *deviceCookie, const char *path, int flags, void **_cookie)
{
TRACE("open(%s)\n", path);
if (strncmp(path, DEVICE_NAME_BASE, strlen(DEVICE_NAME_BASE)) != 0)
return B_NAME_NOT_FOUND;
int32 lastPart = 0;
size_t nameLength = strlen(path);
for (int32 i = nameLength - 1; i >= 0; i--) {
if (path[i] == '/') {
lastPart = i;
break;
}
}
char rawName[nameLength + 4];
strncpy(rawName, path, lastPart + 1);
rawName[lastPart + 1] = 0;
strcat(rawName, "raw");
disk_device *device = (disk_device *)deviceCookie;
MutexLocker locker(device->lock);
for (uint8 i = 0; i < device->lun_count; i++) {
device_lun *lun = device->luns[i];
if (strncmp(rawName, lun->name, 32) == 0) {
if (device->removed)
return B_ERROR;
device->open_count++;
*_cookie = lun;
return B_OK;
}
}
return B_NAME_NOT_FOUND;
}
static status_t
usb_disk_close(void *cookie)
{
TRACE("close()\n");
device_lun *lun = (device_lun *)cookie;
disk_device *device = lun->device;
RecursiveLocker ioLocker(device->io_lock);
MutexLocker deviceLocker(device->lock);
if (!device->removed)
usb_disk_synchronize(lun, false);
return B_OK;
}
static status_t
usb_disk_free(void *cookie)
{
TRACE("free()\n");
device_lun *lun = (device_lun *)cookie;
disk_device *device = lun->device;
mutex_lock(&device->lock);
device->open_count--;
if (device->open_count == 0 && device->removed) {
usb_disk_free_device_and_luns(device);
} else {
mutex_unlock(&device->lock);
}
return B_OK;
}
static inline void
normalize_name(char *name, size_t nameLength)
{
bool wasSpace = false;
size_t insertIndex = 0;
for (size_t i = 0; i < nameLength; i++) {
bool isSpace = name[i] == ' ';
if (isSpace && (wasSpace || insertIndex == 0))
continue;
name[insertIndex++] = name[i];
wasSpace = isSpace;
}
if (insertIndex > 0 && name[insertIndex - 1] == ' ')
insertIndex--;
name[insertIndex] = 0;
}
static status_t
acquire_io_lock(disk_device *device, MutexLocker& locker, RecursiveLocker& ioLocker)
{
locker.Unlock();
ioLocker.SetTo(device->io_lock, false, true);
locker.Lock();
if (!locker.IsLocked() || !ioLocker.IsLocked())
return B_ERROR;
if (device->removed)
return B_DEV_NOT_READY;
return B_OK;
}
static status_t
handle_media_change(device_lun *lun, MutexLocker& locker)
{
RecursiveLocker ioLocker;
status_t result = acquire_io_lock(lun->device, locker, ioLocker);
if (result != B_OK)
return result;
if (lun->media_changed) {
result = usb_disk_update_capacity(lun);
if (result != B_OK)
return result;
}
return B_OK;
}
static status_t
usb_disk_ioctl(void *cookie, uint32 op, void *buffer, size_t length)
{
device_lun *lun = (device_lun *)cookie;
disk_device *device = lun->device;
MutexLocker locker(&device->lock);
if (device->removed)
return B_DEV_NOT_READY;
RecursiveLocker ioLocker;
switch (op) {
case B_GET_DEVICE_SIZE:
{
if (lun->media_changed) {
status_t result = handle_media_change(lun, locker);
if (result != B_OK)
return result;
}
size_t size = lun->block_size * lun->block_count;
return user_memcpy(buffer, &size, sizeof(size));
}
case B_GET_MEDIA_STATUS:
{
status_t result = acquire_io_lock(lun->device, locker, ioLocker);
if (result != B_OK)
return result;
err_act action = err_act_ok;
status_t ready;
for (uint32 tries = 0; tries < 3; tries++) {
ready = usb_disk_test_unit_ready(lun, &action);
if (ready == B_OK || ready == B_DEV_NO_MEDIA
|| (action != err_act_retry
&& action != err_act_many_retries)) {
if (IS_USER_ADDRESS(buffer)) {
if (user_memcpy(buffer, &ready, sizeof(status_t)) != B_OK)
return B_BAD_ADDRESS;
} else if (is_called_via_syscall()) {
return B_BAD_ADDRESS;
} else
*(status_t *)buffer = ready;
break;
}
snooze(500000);
}
TRACE("B_GET_MEDIA_STATUS: 0x%08" B_PRIx32 "\n", ready);
return B_OK;
}
case B_GET_GEOMETRY:
{
if (buffer == NULL || length > sizeof(device_geometry))
return B_BAD_VALUE;
if (lun->media_changed) {
status_t result = handle_media_change(lun, locker);
if (result != B_OK)
return result;
}
device_geometry geometry;
devfs_compute_geometry_size(&geometry, lun->block_count,
lun->block_size);
geometry.bytes_per_physical_sector = lun->physical_block_size;
geometry.device_type = lun->device_type;
geometry.removable = lun->removable;
geometry.read_only = lun->write_protected;
geometry.write_once = lun->device_type == B_WORM;
TRACE("B_GET_GEOMETRY: %" B_PRId32 " sectors at %" B_PRId32
" bytes per sector\n", geometry.cylinder_count,
geometry.bytes_per_sector);
return user_memcpy(buffer, &geometry, length);
}
case B_FLUSH_DRIVE_CACHE:
{
TRACE("B_FLUSH_DRIVE_CACHE\n");
status_t result = acquire_io_lock(lun->device, locker, ioLocker);
if (result != B_OK)
return result;
return usb_disk_synchronize(lun, true);
}
case B_EJECT_DEVICE:
{
status_t result = acquire_io_lock(lun->device, locker, ioLocker);
if (result != B_OK)
return result;
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_START_STOP_UNIT_6;
commandBlock[1] = lun->logical_unit_number << 5;
commandBlock[4] = 2;
return usb_disk_operation(lun, commandBlock, 6, NULL, NULL,
false);
}
case B_LOAD_MEDIA:
{
status_t result = acquire_io_lock(lun->device, locker, ioLocker);
if (result != B_OK)
return result;
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_START_STOP_UNIT_6;
commandBlock[1] = lun->logical_unit_number << 5;
commandBlock[4] = 3;
return usb_disk_operation(lun, commandBlock, 6, NULL, NULL,
false);
}
case B_GET_ICON:
break;
case B_GET_ICON_NAME:
{
const char *iconName = "devices/drive-removable-media-usb";
char vendor[sizeof(lun->vendor_name)+1];
char product[sizeof(lun->product_name)+1];
if (device->is_ufi) {
iconName = "devices/drive-floppy-usb";
}
switch (lun->device_type) {
case B_CD:
case B_OPTICAL:
iconName = "devices/drive-optical";
break;
case B_TAPE:
default:
snprintf(vendor, sizeof(vendor), "%.8s",
lun->vendor_name);
snprintf(product, sizeof(product), "%.16s",
lun->product_name);
for (int i = 0; kIconMatches[i].icon; i++) {
if (kIconMatches[i].vendor != NULL
&& strstr(vendor, kIconMatches[i].vendor) == NULL)
continue;
if (kIconMatches[i].product != NULL
&& strstr(product, kIconMatches[i].product) == NULL)
continue;
iconName = kIconMatches[i].name;
}
break;
}
return user_strlcpy((char *)buffer, iconName,
B_FILE_NAME_LENGTH);
}
case B_GET_VECTOR_ICON:
{
device_icon *icon = &kKeyIconData;
char vendor[sizeof(lun->vendor_name)+1];
char product[sizeof(lun->product_name)+1];
if (length != sizeof(device_icon))
return B_BAD_VALUE;
if (device->is_ufi) {
icon = &kFloppyIconData;
} else {
switch (lun->device_type) {
case B_CD:
case B_OPTICAL:
icon = &kCDIconData;
break;
case B_TAPE:
default:
snprintf(vendor, sizeof(vendor), "%.8s",
lun->vendor_name);
snprintf(product, sizeof(product), "%.16s",
lun->product_name);
for (int i = 0; kIconMatches[i].icon; i++) {
if (kIconMatches[i].vendor != NULL
&& strstr(vendor,
kIconMatches[i].vendor) == NULL)
continue;
if (kIconMatches[i].product != NULL
&& strstr(product,
kIconMatches[i].product) == NULL)
continue;
icon = kIconMatches[i].icon;
}
break;
}
}
device_icon iconData;
if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK)
return B_BAD_ADDRESS;
if (iconData.icon_size >= icon->icon_size) {
if (user_memcpy(iconData.icon_data, icon->icon_data,
(size_t)icon->icon_size) != B_OK)
return B_BAD_ADDRESS;
}
iconData.icon_size = icon->icon_size;
return user_memcpy(buffer, &iconData, sizeof(device_icon));
}
case B_GET_DEVICE_NAME:
{
size_t nameLength = sizeof(lun->vendor_name)
+ sizeof(lun->product_name) + sizeof(lun->product_revision) + 3;
char name[nameLength];
snprintf(name, nameLength, "%.8s %.16s %.4s", lun->vendor_name,
lun->product_name, lun->product_revision);
normalize_name(name, nameLength);
status_t result = user_strlcpy((char *)buffer, name, length);
if (result > 0)
result = B_OK;
TRACE_ALWAYS("got device name \"%s\": %s\n", name,
strerror(result));
return result;
}
}
TRACE_ALWAYS("unhandled ioctl %" B_PRId32 "\n", op);
return B_DEV_INVALID_IOCTL;
}
static status_t
usb_disk_do_io(void* cookie, IOOperation* operation)
{
device_lun *lun = (device_lun *)cookie;
RecursiveLocker ioLocker(lun->device->io_lock);
MutexLocker deviceLocker(lun->device->lock);
TRACE("%p: IOO offset: %" B_PRIdOFF ", length: %" B_PRIuGENADDR
", write: %s\n", operation->Parent(), operation->Offset(),
operation->Length(), operation->IsWrite() ? "yes" : "no");
struct transfer_data data;
data.physical = true;
data.phys_vecs = (physical_entry*)operation->Vecs();
data.vec_count = operation->VecCount();
size_t length = operation->Length();
const uint64 blockPosition = operation->Offset() / lun->block_size;
const size_t blockCount = length / lun->block_size;
status_t status;
if (operation->IsWrite()) {
status = usb_disk_block_write(lun,
blockPosition, blockCount, data, &length);
} else {
status = usb_disk_block_read(lun,
blockPosition, blockCount, data, &length);
}
lun->io_scheduler->OperationCompleted(operation, status, length);
return status;
}
static status_t
usb_disk_io(void *cookie, io_request *request)
{
TRACE("io(%p)\n", request);
device_lun *lun = (device_lun *)cookie;
MutexLocker deviceLocker(lun->device->lock);
if (lun->device->removed)
return B_DEV_NOT_READY;
if (!lun->media_present)
return B_DEV_NO_MEDIA;
if (lun->io_scheduler == NULL) {
return B_BUSY;
}
return lun->io_scheduler->ScheduleRequest(request);
}
static float
usb_disk_supports_device(device_node *parent)
{
CALLED();
const char *bus;
if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
return -1;
if (strcmp(bus, "usb") != 0)
return 0.0;
usb_device device;
if (gDeviceManager->get_attr_uint32(parent, USB_DEVICE_ID_ITEM, &device, true) != B_OK)
return -1;
const usb_configuration_info *configuration = gUSBModule->get_configuration(device);
if (configuration == NULL)
return -1;
static usb_support_descriptor supportedDevices[] = {
{ USB_MASS_STORAGE_DEVICE_CLASS, 0x06 , 0x50 , 0, 0 },
{ USB_MASS_STORAGE_DEVICE_CLASS, 0x02 , 0x50 , 0, 0 },
{ USB_MASS_STORAGE_DEVICE_CLASS, 0x05 , 0x50 , 0, 0 },
{ USB_MASS_STORAGE_DEVICE_CLASS, 0x04 , 0x00, 0, 0 }
};
for (size_t i = 0; i < configuration->interface_count; i++) {
usb_interface_info *interface = configuration->interface[i].active;
if (interface == NULL)
continue;
for (size_t i = 0; i < B_COUNT_OF(supportedDevices); i++) {
if (interface->descr->interface_class != supportedDevices[i].dev_class)
continue;
if (interface->descr->interface_subclass != supportedDevices[i].dev_subclass)
continue;
if (interface->descr->interface_protocol != supportedDevices[i].dev_protocol)
continue;
TRACE("USB disk device found!\n");
return 0.6;
}
}
return 0.0;
}
static status_t
usb_disk_register_device(device_node *node)
{
CALLED();
device_attr attrs[] = {
{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "USB Disk"} },
{ NULL }
};
return gDeviceManager->register_node(node, USB_DISK_DRIVER_MODULE_NAME,
attrs, NULL, NULL);
}
static status_t
usb_disk_init_driver(device_node *node, void **cookie)
{
CALLED();
usb_device usb_device;
if (gDeviceManager->get_attr_uint32(node, USB_DEVICE_ID_ITEM, &usb_device, true) != B_OK)
return B_BAD_VALUE;
return usb_disk_attach(node, usb_device, cookie);
}
static void
usb_disk_uninit_driver(void *_cookie)
{
CALLED();
}
static status_t
usb_disk_register_child_devices(void* _cookie)
{
CALLED();
disk_device *device = (disk_device *)_cookie;
device->number = gDeviceManager->create_id(USB_DISK_DEVICE_ID_GENERATOR);
if (device->number < 0)
return device->number;
status_t status = B_OK;
for (uint8 i = 0; i < device->lun_count; i++) {
sprintf(device->luns[i]->name, DEVICE_NAME, device->number, i);
status = gDeviceManager->publish_device(device->node, device->luns[i]->name,
USB_DISK_DEVICE_MODULE_NAME);
}
return status;
}
module_dependency module_dependencies[] = {
{ B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager },
{ B_USB_MODULE_NAME, (module_info**)&gUSBModule},
{ NULL }
};
struct device_module_info sUsbDiskDevice = {
{
USB_DISK_DEVICE_MODULE_NAME,
0,
NULL
},
usb_disk_init_device,
usb_disk_uninit_device,
usb_disk_device_removed,
usb_disk_open,
usb_disk_close,
usb_disk_free,
NULL,
NULL,
usb_disk_io,
usb_disk_ioctl,
NULL,
NULL,
};
struct driver_module_info sUsbDiskDriver = {
{
USB_DISK_DRIVER_MODULE_NAME,
0,
NULL
},
usb_disk_supports_device,
usb_disk_register_device,
usb_disk_init_driver,
usb_disk_uninit_driver,
usb_disk_register_child_devices,
NULL,
NULL,
};
module_info* modules[] = {
(module_info*)&sUsbDiskDriver,
(module_info*)&sUsbDiskDevice,
NULL
};