* Copyright 2008-2009, Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Michael Lotz <mmlr@mlotz.ch>
*/
#include <ByteOrder.h>
#include <KernelExport.h>
#include <Drivers.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include "usb_disk.h"
#include "usb_disk_scsi.h"
#define DRIVER_NAME "usb_floppy"
#define DEVICE_NAME_BASE "disk/ufi/"
#define DEVICE_NAME DEVICE_NAME_BASE"%ld/%d/raw"
#ifdef TRACE_USB_DISK
#define TRACE(x...) dprintf("\x1B[35m"DRIVER_NAME"\x1B[0m: "x)
#define TRACE_ALWAYS(x...) dprintf("\x1B[35m"DRIVER_NAME"\x1B[0m: "x)
#else
#define TRACE(x...)
#define TRACE_ALWAYS(x...) dprintf("\x1B[35m"DRIVER_NAME"\x1B[0m: "x)
#endif
int32 api_version = B_CUR_DRIVER_API_VERSION;
static usb_module_info *gUSBModule = NULL;
static disk_device *gDeviceList = NULL;
static uint32 gDeviceCount = 0;
static uint32 gLunCount = 0;
static mutex gDeviceListLock;
static char **gDeviceNames = NULL;
static uint8 kDeviceIcon[] = {
0x6e, 0x63, 0x69, 0x66, 0x0c, 0x03, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00,
0x00, 0x6a, 0x02, 0x00, 0x16, 0x02, 0x38, 0x6a, 0xad, 0x38, 0xeb, 0x7a,
0xbb, 0x77, 0x73, 0x3a, 0xde, 0x88, 0x48, 0xce, 0xdc, 0x4a, 0x75, 0xed,
0x00, 0x8a, 0xff, 0x4b, 0x02, 0x00, 0x16, 0x02, 0x38, 0x6a, 0xad, 0x38,
0xeb, 0x7a, 0xbb, 0x77, 0x73, 0x3a, 0xde, 0x88, 0x48, 0xce, 0xdc, 0x4a,
0x75, 0xed, 0x00, 0x8a, 0xff, 0x2c, 0x05, 0x7d, 0x02, 0x00, 0x16, 0x02,
0x39, 0xc6, 0x30, 0x36, 0xa8, 0x1b, 0xb9, 0x51, 0xe6, 0x3c, 0x5b, 0xb3,
0x4b, 0x4d, 0xa8, 0x4a, 0x1a, 0xa1, 0x00, 0x3a, 0xff, 0x78, 0x02, 0x00,
0x16, 0x02, 0xb7, 0x16, 0xa4, 0xba, 0x87, 0xfe, 0x3c, 0xb2, 0x07, 0xb9,
0x49, 0xed, 0x48, 0x87, 0xd8, 0x4a, 0x1e, 0x62, 0x01, 0x75, 0xfe, 0xc4,
0x02, 0x00, 0x16, 0x02, 0xb7, 0x16, 0xa4, 0xba, 0x87, 0xfe, 0x3c, 0xb2,
0x07, 0xb9, 0x49, 0xed, 0x48, 0x87, 0xd8, 0x4a, 0x1e, 0x62, 0x01, 0x5c,
0xfe, 0x9b, 0x02, 0x00, 0x16, 0x05, 0x36, 0xef, 0x60, 0x38, 0xe2, 0xe5,
0xbd, 0x22, 0x41, 0x3b, 0x2f, 0xce, 0x4a, 0x0e, 0x78, 0x4a, 0x5a, 0x6c,
0x00, 0xb8, 0x38, 0xe6, 0x77, 0xb8, 0xbd, 0xd2, 0xff, 0x93, 0x02, 0x00,
0x16, 0x05, 0x34, 0x0a, 0x8f, 0x38, 0xd2, 0xa2, 0xba, 0xb4, 0xc5, 0x35,
0xe9, 0xec, 0x49, 0xfd, 0x24, 0x4a, 0x64, 0x62, 0x00, 0xe1, 0x38, 0xff,
0x75, 0xd7, 0xba, 0xf3, 0xfd, 0xd0, 0x02, 0x00, 0x16, 0x02, 0x36, 0x67,
0xbe, 0x39, 0xdd, 0xbc, 0xbe, 0x50, 0x04, 0x3a, 0xe0, 0x9f, 0x4b, 0x85,
0x01, 0x49, 0x2a, 0x3f, 0x00, 0xff, 0xff, 0xd8, 0x03, 0x38, 0x6d, 0xbe,
0x12, 0x0a, 0x05, 0x44, 0x5c, 0x44, 0x40, 0x5e, 0x40, 0x62, 0x42, 0x48,
0x5c, 0x0a, 0x06, 0x45, 0x58, 0x5c, 0x42, 0x5c, 0x40, 0x3c, 0x32, 0xb5,
0x6b, 0xc2, 0x2e, 0xb5, 0x63, 0xc3, 0xbb, 0x0a, 0x04, 0x44, 0x58, 0x26,
0x4a, 0x26, 0x47, 0x44, 0x54, 0x0a, 0x04, 0x44, 0x59, 0x5c, 0x42, 0x5c,
0x3e, 0x44, 0x54, 0x0a, 0x05, 0x44, 0x56, 0x5c, 0x40, 0x3c, 0x32, 0xb5,
0x6b, 0xc2, 0x2e, 0xb5, 0x43, 0xc3, 0x3b, 0x0a, 0x04, 0x2a, 0x4c, 0x3f,
0x56, 0x3f, 0x53, 0x2a, 0x4a, 0x0a, 0x04, 0x2a, 0x4b, 0x39, 0x52, 0x39,
0x50, 0x2a, 0x4a, 0x0a, 0x04, 0x31, 0x42, 0x45, 0x4c, 0x3f, 0x53, 0x2a,
0x4a, 0x0a, 0x04, 0x3f, 0x49, 0x39, 0x50, 0x2a, 0x4a, 0x31, 0x43, 0x0a,
0x04, 0x40, 0x34, 0x58, 0x3e, 0x49, 0x4b, 0x32, 0x40, 0x08, 0x02, 0x3c,
0x3b, 0x4d, 0x43, 0x00, 0x02, 0x3c, 0x3b, 0x32, 0x36, 0x44, 0x3f, 0x4a,
0x3c, 0xc2, 0x73, 0xbd, 0x83, 0x4e, 0x3e, 0x00, 0x02, 0x41, 0x3e, 0xbd,
0xea, 0xbe, 0x50, 0x48, 0x40, 0x47, 0x45, 0x42, 0x43, 0xc4, 0x31, 0xc2,
0x67, 0x0a, 0x04, 0x32, 0x4b, 0x36, 0x48, 0x32, 0x46, 0x2e, 0x4a, 0x02,
0x04, 0x39, 0x3b, 0x39, 0x3d, 0x39, 0x39, 0x3c, 0x38, 0x3a, 0x38, 0x3e,
0x38, 0x3f, 0x3b, 0x3f, 0x39, 0x3f, 0x3d, 0x3c, 0x3e, 0xbe, 0xf3, 0x3e,
0x3a, 0x3e, 0x02, 0x04, 0x48, 0x3c, 0x48, 0x3e, 0x48, 0x3a, 0x4a, 0x3a,
0x48, 0x3a, 0x4c, 0x3a, 0x4c, 0x3c, 0x4c, 0x3a, 0x4c, 0x3e, 0x4a, 0x3e,
0x4c, 0x3e, 0x48, 0x3e, 0x0a, 0x04, 0x47, 0x43, 0x44, 0x45, 0x47, 0x48,
0x4a, 0x46, 0x0a, 0x03, 0x4d, 0x43, 0x4c, 0x3f, 0x48, 0x43, 0x0d, 0x0a,
0x01, 0x01, 0x00, 0x20, 0x1e, 0x20, 0x0a, 0x00, 0x01, 0x01, 0x30, 0x1e,
0x20, 0x01, 0x17, 0x84, 0x00, 0x04, 0x0a, 0x02, 0x01, 0x02, 0x20, 0x1e,
0x20, 0x0a, 0x05, 0x01, 0x03, 0x20, 0x1e, 0x20, 0x0a, 0x06, 0x01, 0x04,
0x20, 0x1e, 0x20, 0x0a, 0x03, 0x01, 0x05, 0x20, 0x1e, 0x20, 0x0a, 0x07,
0x01, 0x07, 0x20, 0x1e, 0x20, 0x0a, 0x08, 0x01, 0x06, 0x20, 0x1e, 0x20,
0x0a, 0x09, 0x01, 0x08, 0x20, 0x1e, 0x20, 0x0a, 0x0a, 0x01, 0x09, 0x20,
0x1e, 0x20, 0x0a, 0x00, 0x03, 0x0a, 0x0b, 0x0c, 0x18, 0x20, 0xff, 0x01,
0x17, 0x81, 0x00, 0x04, 0x0a, 0x02, 0x01, 0x0d, 0x20, 0x1e, 0x20, 0x0a,
0x00, 0x04, 0x0e, 0x0f, 0x10, 0x11, 0x0a, 0x3f, 0xff, 0xbd, 0x34, 0xe0,
0x2c, 0xb4, 0xe0, 0x2c, 0x3f, 0xff, 0xbd, 0x3e, 0x74, 0x62, 0xbe, 0x7d,
0x2c, 0x20, 0xff
};
static void usb_disk_callback(void *cookie, status_t status, void *data,
size_t actualLength);
status_t usb_disk_mass_storage_reset(disk_device *device);
uint8 usb_disk_get_max_lun(disk_device *device);
void usb_disk_reset_recovery(disk_device *device);
status_t usb_disk_transfer_data(disk_device *device, bool directionIn,
void *data, size_t dataLength);
status_t usb_disk_receive_csw(disk_device *device,
command_status_wrapper *status);
status_t usb_disk_operation(device_lun *lun, uint8* operation,
void *data, uint32 *dataLength,
bool directionIn);
status_t usb_disk_send_diagnostic(device_lun *lun);
status_t usb_disk_request_sense(device_lun *lun);
status_t usb_disk_mode_sense(device_lun *lun);
status_t usb_disk_test_unit_ready(device_lun *lun);
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);
void
usb_disk_free_device_and_luns(disk_device *device)
{
mutex_lock(&device->lock);
mutex_destroy(&device->lock);
delete_sem(device->notify);
delete_sem(device->interruptLock);
for (uint8 i = 0; i < device->lun_count; i++)
free(device->luns[i]);
free(device->luns);
free(device);
}
void
usb_disk_reset_recovery(disk_device *device)
{
gUSBModule->clear_feature(device->bulk_in, USB_FEATURE_ENDPOINT_HALT);
gUSBModule->clear_feature(device->bulk_out, USB_FEATURE_ENDPOINT_HALT);
gUSBModule->clear_feature(device->interrupt, USB_FEATURE_ENDPOINT_HALT);
}
status_t
usb_disk_transfer_data(disk_device *device, bool directionIn, void *data,
size_t dataLength)
{
status_t result = gUSBModule->queue_bulk(directionIn ? device->bulk_in
: device->bulk_out, data, dataLength, usb_disk_callback, device);
if (result != B_OK) {
TRACE_ALWAYS("failed to queue data transfer\n");
return result;
}
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);
if (result != B_OK) {
TRACE_ALWAYS("acquire_sem failed while waiting for data transfer\n");
return result;
}
return B_OK;
}
void
usb_disk_interrupt(void* cookie, int32 status, void* data, uint32 length)
{
disk_device* dev = (disk_device*)cookie;
release_sem(dev->interruptLock);
if (length != 2) {
TRACE_ALWAYS("interrupt of length %ld! (expected 2)\n", length);
return;
}
gUSBModule->queue_interrupt(dev->interrupt, dev->interruptBuffer, 2,
usb_disk_interrupt, cookie);
}
status_t
usb_disk_receive_csw(disk_device *device, command_status_wrapper *status)
{
TRACE("Waiting for result...\n");
gUSBModule->queue_interrupt(device->interrupt,
device->interruptBuffer, 2, usb_disk_interrupt, device);
acquire_sem(device->interruptLock);
status->status = device->interruptBuffer[0];
status->misc = device->interruptBuffer[1];
return B_OK;
}
status_t
usb_disk_operation(device_lun *lun, uint8* operation,
void *data, uint32 *dataLength, bool directionIn)
{
TRACE("operation: lun: %u; op: 0x%x; data: %p; dlen: %p (%lu); in: %c\n",
lun->logical_unit_number, operation[0], data, dataLength,
dataLength ? *dataLength : 0, directionIn ? 'y' : 'n');
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);
}
return result;
}
size_t transferedData = 0;
if (data != NULL && dataLength != NULL && *dataLength > 0) {
result = usb_disk_transfer_data(device, directionIn, data, *dataLength);
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");
gUSBModule->clear_feature(directionIn ? device->bulk_in
: device->bulk_out, USB_FEATURE_ENDPOINT_HALT);
} else {
TRACE_ALWAYS("sending or receiving of the data failed\n");
usb_disk_reset_recovery(device);
return B_ERROR;
}
}
}
if (operation[0] != SCSI_REQUEST_SENSE_6) {
command_status_wrapper status;
result = usb_disk_receive_csw(device, &status);
if (result != B_OK) {
TRACE("Error receiving interrupt: %s. Retrying...\n", strerror(result));
gUSBModule->clear_feature(device->bulk_in, USB_FEATURE_ENDPOINT_HALT);
result = usb_disk_receive_csw(device, &status);
}
if (result != B_OK) {
TRACE_ALWAYS("receiving the command status interrupt failed\n");
usb_disk_reset_recovery(device);
return result;
}
result = usb_disk_request_sense(lun);
}
return result;
}
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, NULL,
NULL, false);
int retry = 100;
while(result == B_DEV_NO_MEDIA && retry > 0) {
snooze(10000);
result = usb_disk_request_sense(lun);
retry--;
}
if (result != B_OK)
TRACE("Send Diagnostic failed: %s\n", strerror(result));
return result;
}
status_t
usb_disk_request_sense(device_lun *lun)
{
uint32 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 = usb_disk_operation(lun, commandBlock, ¶meter,
&dataLength, true);
if (result != B_OK) {
TRACE_ALWAYS("getting request sense data failed\n");
return result;
}
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;\n",
parameter.sense_key, parameter.additional_sense_code,
parameter.additional_sense_code_qualifier);
}
switch (parameter.sense_key) {
case SCSI_SENSE_KEY_NO_SENSE:
case SCSI_SENSE_KEY_RECOVERED_ERROR:
return B_OK;
case SCSI_SENSE_KEY_HARDWARE_ERROR:
case SCSI_SENSE_KEY_MEDIUM_ERROR:
TRACE_ALWAYS("request_sense: media or hardware error\n");
return B_DEV_UNREADABLE;
case SCSI_SENSE_KEY_ILLEGAL_REQUEST:
TRACE_ALWAYS("request_sense: illegal request\n");
return B_DEV_INVALID_IOCTL;
case SCSI_SENSE_KEY_UNIT_ATTENTION:
if (parameter.additional_sense_code != SCSI_ASC_MEDIUM_NOT_PRESENT) {
TRACE_ALWAYS("request_sense: media changed\n");
lun->media_changed = true;
lun->media_present = true;
return B_DEV_MEDIA_CHANGED;
}
case SCSI_SENSE_KEY_NOT_READY:
TRACE("request_sense: device not ready (asc 0x%02x ascq 0x%02x)\n",
parameter.additional_sense_code,
parameter.additional_sense_code_qualifier);
lun->media_present = false;
usb_disk_reset_capacity(lun);
return B_DEV_NO_MEDIA;
case SCSI_SENSE_KEY_DATA_PROTECT:
TRACE_ALWAYS("request_sense: write protected\n");
return B_READ_ONLY_DEVICE;
case SCSI_SENSE_KEY_ABORTED_COMMAND:
TRACE_ALWAYS("request_sense: command aborted\n");
return B_CANCELED;
}
return B_ERROR;
}
status_t
usb_disk_mode_sense(device_lun *lun)
{
uint32 dataLength = sizeof(scsi_mode_sense_6_parameter);
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_MODE_SENSE_6;
commandBlock[1] = lun->logical_unit_number << 5;
commandBlock[2] = 0;
commandBlock[7] = dataLength >> 8;
commandBlock[8] = dataLength;
scsi_mode_sense_6_parameter parameter;
status_t result = usb_disk_operation(lun, commandBlock,
¶meter, &dataLength, true);
if (result != B_OK) {
TRACE_ALWAYS("getting mode sense data failed\n");
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)
{
return B_OK;
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_TEST_UNIT_READY_6;
commandBlock[1] = lun->logical_unit_number << 5;
status_t result = usb_disk_operation(lun, commandBlock, NULL, NULL, false);
return result;
}
status_t
usb_disk_inquiry(device_lun *lun)
{
uint32 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;
for (uint32 tries = 0; tries < 3; tries++) {
result = usb_disk_operation(lun, commandBlock, ¶meter, &dataLength,
true);
if (result == B_OK)
break;
}
if (result != B_OK) {
TRACE_ALWAYS("getting inquiry data failed\n");
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);
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;
}
status_t
usb_disk_update_capacity(device_lun *lun)
{
uint32 dataLength = sizeof(scsi_read_capacity_10_parameter);
scsi_read_capacity_10_parameter parameter;
status_t result = B_ERROR;
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
commandBlock[0] = SCSI_READ_CAPACITY_10;
commandBlock[1] = lun->logical_unit_number << 5;
for (int tries = 0; tries < 5; tries++) {
result = usb_disk_operation(lun, commandBlock, ¶meter, &dataLength,
true);
if (result == B_DEV_NO_MEDIA || result == B_TIMED_OUT
|| result == B_DEV_STALLED)
snooze(10000);
else
break;
}
if (result != B_OK) {
TRACE_ALWAYS("failed to update capacity\n");
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 = ntohl(parameter.logical_block_length);
lun->block_count = ntohl(parameter.last_logical_block_address) + 1;
return B_OK;
}
status_t
usb_disk_synchronize(device_lun *lun, bool force)
{
return B_UNSUPPORTED;
}
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_device_added(usb_device newDevice, void **cookie)
{
TRACE("device_added(0x%08lx)\n", newDevice);
disk_device *device = (disk_device *)malloc(sizeof(disk_device));
device->device = newDevice;
device->removed = false;
device->open_count = 0;
device->interface = 0xff;
device->current_tag = 0;
device->sync_support = SYNC_SUPPORT_RELOAD;
device->is_atapi = false;
device->luns = NULL;
const usb_configuration_info *configuration
= gUSBModule->get_configuration(newDevice);
if (configuration == NULL) {
free(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 == 0x08
&& 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)
&& 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 && hasInt)) {
TRACE("in: %d, out: %d, int: %d\n", hasIn, hasOut, hasInt);
continue;
}
device->interface = interface->descr->interface_number;
device->is_atapi = interface->descr->interface_subclass != 0x06;
break;
}
}
if (device->interface == 0xff) {
TRACE_ALWAYS("no valid CBI interface found\n");
free(device);
return B_ERROR;
}
mutex_init(&device->lock, "usb_disk device lock");
device->notify = create_sem(0, "usb_disk callback notify");
if (device->notify < B_OK) {
mutex_destroy(&device->lock);
free(device);
return device->notify;
}
device->interruptLock = create_sem(0, "usb_disk interrupt lock");
if (device->interruptLock < B_OK) {
mutex_destroy(&device->lock);
free(device);
return device->interruptLock;
}
device->lun_count = 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;
usb_disk_reset_capacity(lun);
result = usb_disk_inquiry(lun);
result = usb_disk_send_diagnostic(lun);
for (uint32 tries = 0; tries < 3; tries++) {
status_t ready = usb_disk_test_unit_ready(lun);
if (ready == B_OK || ready == B_DEV_NO_MEDIA) {
if (ready == B_OK) {
if (lun->device_type == B_CD)
lun->write_protected = true;
else if (true)
lun->write_protected = false;
}
break;
}
snooze(10000);
}
if (result != B_OK)
break;
}
if (result != B_OK) {
TRACE_ALWAYS("failed to initialize logical units\n");
usb_disk_free_device_and_luns(device);
return result;
}
mutex_lock(&gDeviceListLock);
device->device_number = 0;
disk_device *other = gDeviceList;
while (other != NULL) {
if (other->device_number >= device->device_number)
device->device_number = other->device_number + 1;
other = (disk_device *)other->link;
}
device->link = (void *)gDeviceList;
gDeviceList = device;
gLunCount += device->lun_count;
for (uint8 i = 0; i < device->lun_count; i++)
sprintf(device->luns[i]->name, DEVICE_NAME, device->device_number, i);
mutex_unlock(&gDeviceListLock);
TRACE("new device: 0x%08lx\n", (uint32)device);
*cookie = (void *)device;
return B_OK;
}
static status_t
usb_disk_device_removed(void *cookie)
{
TRACE("device_removed(0x%08lx)\n", (uint32)cookie);
disk_device *device = (disk_device *)cookie;
mutex_lock(&gDeviceListLock);
if (gDeviceList == device) {
gDeviceList = (disk_device *)device->link;
} else {
disk_device *element = gDeviceList;
while (element) {
if (element->link == device) {
element->link = device->link;
break;
}
element = (disk_device *)element->link;
}
}
gLunCount -= device->lun_count;
gDeviceCount--;
device->removed = true;
gUSBModule->cancel_queued_transfers(device->bulk_in);
gUSBModule->cancel_queued_transfers(device->bulk_out);
if (device->open_count == 0)
usb_disk_free_device_and_luns(device);
mutex_unlock(&gDeviceListLock);
return B_OK;
}
static bool
usb_disk_needs_partial_buffer(device_lun *lun, off_t position, size_t length,
uint32 &blockPosition, uint16 &blockCount)
{
blockPosition = (uint32)(position / lun->block_size);
if ((off_t)blockPosition * lun->block_size != position)
return true;
blockCount = (uint16)(length / lun->block_size);
if ((size_t)blockCount * lun->block_size != length)
return true;
return false;
}
static status_t
usb_disk_block_read(device_lun *lun, uint32 blockPosition, uint16 blockCount,
void *buffer, size_t *length)
{
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
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, buffer, length,
true);
if (result == B_OK)
break;
else
snooze(10000);
}
return result;
}
static status_t
usb_disk_block_write(device_lun *lun, uint32 blockPosition, uint16 blockCount,
void *buffer, size_t *length)
{
uint8 commandBlock[12];
memset(commandBlock, 0, sizeof(commandBlock));
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, buffer, length,
false);
int retry = 10;
while (result == B_DEV_NO_MEDIA && retry > 0) {
snooze(10000);
result = usb_disk_request_sense(lun);
retry--;
}
if (result == B_OK)
lun->should_sync = true;
return result;
}
static status_t
usb_disk_prepare_partial_buffer(device_lun *lun, off_t position, size_t length,
void *&partialBuffer, void *&blockBuffer, uint32 &blockPosition,
uint16 &blockCount)
{
blockPosition = (uint32)(position / lun->block_size);
blockCount = (uint16)((uint32)((position + length + lun->block_size - 1)
/ lun->block_size) - blockPosition);
size_t blockLength = blockCount * lun->block_size;
blockBuffer = malloc(blockLength);
if (blockBuffer == NULL) {
TRACE_ALWAYS("no memory to allocate partial buffer\n");
return B_NO_MEMORY;
}
status_t result = usb_disk_block_read(lun, blockPosition, blockCount,
blockBuffer, &blockLength);
if (result != B_OK) {
TRACE_ALWAYS("block read failed when filling partial buffer\n");
free(blockBuffer);
return result;
}
off_t offset = position - (blockPosition * lun->block_size);
partialBuffer = (uint8 *)blockBuffer + offset;
return B_OK;
}
static status_t
usb_disk_open(const char *name, uint32 flags, void **cookie)
{
TRACE("open(%s)\n", name);
if (strncmp(name, DEVICE_NAME_BASE, strlen(DEVICE_NAME_BASE)) != 0)
return B_NAME_NOT_FOUND;
int32 lastPart = 0;
size_t nameLength = strlen(name);
for (int32 i = nameLength - 1; i >= 0; i--) {
if (name[i] == '/') {
lastPart = i;
break;
}
}
char rawName[nameLength + 4];
strncpy(rawName, name, lastPart + 1);
rawName[lastPart + 1] = 0;
strcat(rawName, "raw");
TRACE("opening raw device %s for %s\n", rawName, name);
mutex_lock(&gDeviceListLock);
disk_device *device = gDeviceList;
while (device) {
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) {
mutex_unlock(&gDeviceListLock);
return B_ERROR;
}
device->open_count++;
*cookie = lun;
mutex_unlock(&gDeviceListLock);
return B_OK;
}
}
device = (disk_device *)device->link;
}
mutex_unlock(&gDeviceListLock);
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;
mutex_lock(&device->lock);
if (!device->removed)
usb_disk_synchronize(lun, false);
mutex_unlock(&device->lock);
return B_OK;
}
static status_t
usb_disk_free(void *cookie)
{
TRACE("free()\n");
mutex_lock(&gDeviceListLock);
device_lun *lun = (device_lun *)cookie;
disk_device *device = lun->device;
device->open_count--;
if (device->open_count == 0 && device->removed) {
usb_disk_free_device_and_luns(device);
}
mutex_unlock(&gDeviceListLock);
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;
mutex_lock(&device->lock);
if (device->removed) {
mutex_unlock(&device->lock);
return B_DEV_NOT_READY;
}
status_t result = B_DEV_INVALID_IOCTL;
switch (op) {
case B_GET_MEDIA_STATUS: {
*(status_t *)buffer = usb_disk_test_unit_ready(lun);
TRACE("B_GET_MEDIA_STATUS: 0x%08lx\n", *(status_t *)buffer);
result = B_OK;
break;
}
case B_GET_GEOMETRY: {
if (lun->media_changed) {
result = usb_disk_update_capacity(lun);
if (result != B_OK)
break;
}
device_geometry *geometry = (device_geometry *)buffer;
geometry->bytes_per_sector = lun->block_size;
geometry->cylinder_count = lun->block_count;
geometry->sectors_per_track = geometry->head_count = 1;
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: %ld sectors at %ld bytes per sector\n",
geometry->cylinder_count, geometry->bytes_per_sector);
result = B_OK;
break;
}
case B_FLUSH_DRIVE_CACHE:
TRACE("B_FLUSH_DRIVE_CACHE\n");
result = usb_disk_synchronize(lun, true);
break;
case B_EJECT_DEVICE:
{
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;
result = usb_disk_operation(lun, commandBlock, NULL, NULL, false);
break;
}
case B_LOAD_MEDIA:
{
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;
result = usb_disk_operation(lun, commandBlock, NULL, NULL, false);
break;
}
#if HAIKU_TARGET_PLATFORM_HAIKU
case B_GET_ICON:
break;
case B_GET_ICON_NAME:
result = user_strlcpy((char *)buffer,
"devices/drive-removable-media-usb", B_FILE_NAME_LENGTH);
break;
case B_GET_VECTOR_ICON:
{
if (length != sizeof(device_icon)) {
result = B_BAD_VALUE;
break;
}
device_icon iconData;
if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK) {
result = B_BAD_ADDRESS;
break;
}
if (iconData.icon_size >= (int32)sizeof(kDeviceIcon)) {
if (user_memcpy(iconData.icon_data, kDeviceIcon,
sizeof(kDeviceIcon)) != B_OK) {
result = B_BAD_ADDRESS;
break;
}
}
iconData.icon_size = sizeof(kDeviceIcon);
result = user_memcpy(buffer, &iconData, sizeof(device_icon));
break;
}
#endif
default:
TRACE_ALWAYS("unhandled ioctl %ld\n", op);
break;
}
mutex_unlock(&device->lock);
return result;
}
static status_t
usb_disk_read(void *cookie, off_t position, void *buffer, size_t *length)
{
if (buffer == NULL || length == NULL)
return B_BAD_VALUE;
TRACE("read(%lld, %ld)\n", position, *length);
device_lun *lun = (device_lun *)cookie;
disk_device *device = lun->device;
mutex_lock(&device->lock);
if (device->removed) {
*length = 0;
mutex_unlock(&device->lock);
return B_DEV_NOT_READY;
}
status_t result = B_ERROR;
uint32 blockPosition = 0;
uint16 blockCount = 0;
bool needsPartial = usb_disk_needs_partial_buffer(lun, position, *length,
blockPosition, blockCount);
if (needsPartial) {
void *partialBuffer = NULL;
void *blockBuffer = NULL;
result = usb_disk_prepare_partial_buffer(lun, position, *length,
partialBuffer, blockBuffer, blockPosition, blockCount);
if (result == B_OK) {
memcpy(buffer, partialBuffer, *length);
free(blockBuffer);
}
} else {
result = usb_disk_block_read(lun, blockPosition, blockCount, buffer,
length);
}
mutex_unlock(&device->lock);
if (result == B_OK) {
TRACE("read successful with %ld bytes\n", *length);
return B_OK;
}
*length = 0;
TRACE_ALWAYS("read fails with 0x%08lx\n", result);
return result;
}
static status_t
usb_disk_write(void *cookie, off_t position, const void *buffer,
size_t *length)
{
if (buffer == NULL || length == NULL)
return B_BAD_VALUE;
TRACE("write(%lld, %ld)\n", position, *length);
device_lun *lun = (device_lun *)cookie;
disk_device *device = lun->device;
mutex_lock(&device->lock);
if (device->removed) {
*length = 0;
mutex_unlock(&device->lock);
return B_DEV_NOT_READY;
}
status_t result = B_ERROR;
uint32 blockPosition = 0;
uint16 blockCount = 0;
bool needsPartial = usb_disk_needs_partial_buffer(lun, position,
*length, blockPosition, blockCount);
if (needsPartial) {
void *partialBuffer = NULL;
void *blockBuffer = NULL;
result = usb_disk_prepare_partial_buffer(lun, position, *length,
partialBuffer, blockBuffer, blockPosition, blockCount);
if (result == B_OK) {
memcpy(partialBuffer, buffer, *length);
size_t blockLength = blockCount * lun->block_size;
result = usb_disk_block_write(lun, blockPosition, blockCount,
blockBuffer, &blockLength);
free(blockBuffer);
}
} else {
result = usb_disk_block_write(lun, blockPosition, blockCount,
(void *)buffer, length);
}
mutex_unlock(&device->lock);
if (result == B_OK) {
TRACE("write successful with %ld bytes\n", *length);
return B_OK;
}
*length = 0;
TRACE_ALWAYS("write fails with 0x%08lx\n", result);
return result;
}
status_t
init_hardware()
{
TRACE("init_hardware()\n");
return B_OK;
}
status_t
init_driver()
{
TRACE("init_driver()\n");
static usb_notify_hooks notifyHooks = {
&usb_disk_device_added,
&usb_disk_device_removed
};
static usb_support_descriptor supportedDevices[] = {
{ 0x08 , 0x04 , 0x00, 0, 0 }
};
gDeviceList = NULL;
gDeviceCount = 0;
gLunCount = 0;
mutex_init(&gDeviceListLock, "usb_disk device list lock");
TRACE("trying module %s\n", B_USB_MODULE_NAME);
status_t result = get_module(B_USB_MODULE_NAME,
(module_info **)&gUSBModule);
if (result < B_OK) {
TRACE_ALWAYS("getting module failed 0x%08lx\n", result);
mutex_destroy(&gDeviceListLock);
return result;
}
gUSBModule->register_driver(DRIVER_NAME, supportedDevices, 3, NULL);
gUSBModule->install_notify(DRIVER_NAME, ¬ifyHooks);
return B_OK;
}
void
uninit_driver()
{
TRACE("uninit_driver()\n");
gUSBModule->uninstall_notify(DRIVER_NAME);
mutex_lock(&gDeviceListLock);
if (gDeviceNames) {
for (int32 i = 0; gDeviceNames[i]; i++)
free(gDeviceNames[i]);
free(gDeviceNames);
gDeviceNames = NULL;
}
mutex_destroy(&gDeviceListLock);
put_module(B_USB_MODULE_NAME);
}
const char **
publish_devices()
{
TRACE("publish_devices()\n");
if (gDeviceNames) {
for (int32 i = 0; gDeviceNames[i]; i++)
free(gDeviceNames[i]);
free(gDeviceNames);
gDeviceNames = NULL;
}
gDeviceNames = (char **)malloc(sizeof(char *) * (gLunCount + 1));
if (gDeviceNames == NULL)
return NULL;
int32 index = 0;
mutex_lock(&gDeviceListLock);
disk_device *device = gDeviceList;
while (device) {
for (uint8 i = 0; i < device->lun_count; i++)
gDeviceNames[index++] = strdup(device->luns[i]->name);
device = (disk_device *)device->link;
}
gDeviceNames[index++] = NULL;
mutex_unlock(&gDeviceListLock);
return (const char **)gDeviceNames;
}
device_hooks *
find_device(const char *name)
{
TRACE("find_device()\n");
static device_hooks hooks = {
&usb_disk_open,
&usb_disk_close,
&usb_disk_free,
&usb_disk_ioctl,
&usb_disk_read,
&usb_disk_write,
NULL,
NULL,
NULL,
NULL
};
return &hooks;
}