fe4ffbd94c
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@16285 a1c6a512-1295-4272-9138-f99709370657
512 lines
17 KiB
C
512 lines
17 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id: $
|
|
*
|
|
* Copyright (C) 2007 by Björn Stenberg
|
|
*
|
|
* All files in this archive are subject to the GNU General Public License.
|
|
* See the file COPYING in the source tree root for full license agreement.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include "string.h"
|
|
#include "system.h"
|
|
#include "usb_core.h"
|
|
#include "usb_drv.h"
|
|
//#define LOGF_ENABLE
|
|
#include "logf.h"
|
|
#include "ata.h"
|
|
#include "hotswap.h"
|
|
#include "disk.h"
|
|
|
|
#define SECTOR_SIZE 512
|
|
|
|
/* bulk-only class specific requests */
|
|
#define USB_BULK_RESET_REQUEST 0xff
|
|
#define USB_BULK_GET_MAX_LUN 0xfe
|
|
|
|
#define DIRECT_ACCESS_DEVICE 0x00 /* disks */
|
|
#define DEVICE_REMOVABLE 0x80
|
|
|
|
#define CBW_SIGNATURE 0x43425355
|
|
#define CSW_SIGNATURE 0x53425355
|
|
|
|
#define SCSI_TEST_UNIT_READY 0x00
|
|
#define SCSI_INQUIRY 0x12
|
|
#define SCSI_MODE_SENSE 0x1a
|
|
#define SCSI_REQUEST_SENSE 0x03
|
|
#define SCSI_ALLOW_MEDIUM_REMOVAL 0x1e
|
|
#define SCSI_READ_CAPACITY 0x25
|
|
#define SCSI_READ_FORMAT_CAPACITY 0x23
|
|
#define SCSI_READ_10 0x28
|
|
#define SCSI_WRITE_10 0x2a
|
|
#define SCSI_START_STOP_UNIT 0x1b
|
|
|
|
#define SCSI_STATUS_GOOD 0x00
|
|
#define SCSI_STATUS_FAIL 0x01
|
|
#define SCSI_STATUS_CHECK_CONDITION 0x02
|
|
|
|
#define SCSI_FORMAT_CAPACITY_FORMATTED_MEDIA 0x02000000
|
|
|
|
|
|
struct inquiry_data {
|
|
unsigned char DeviceType;
|
|
unsigned char DeviceTypeModifier;
|
|
unsigned char Versions;
|
|
unsigned char Format;
|
|
unsigned char AdditionalLength;
|
|
unsigned char Reserved[2];
|
|
unsigned char Capability;
|
|
unsigned char VendorId[8];
|
|
unsigned char ProductId[16];
|
|
unsigned char ProductRevisionLevel[4];
|
|
} __attribute__ ((packed));
|
|
|
|
struct sense_data {
|
|
unsigned char ResponseCode;
|
|
unsigned char Obsolete;
|
|
unsigned char filemark_eom_ili_sensekey;
|
|
unsigned int Information;
|
|
unsigned char AdditionalSenseLength;
|
|
unsigned int CommandSpecificInformation;
|
|
unsigned char AdditionalSenseCode;
|
|
unsigned char AdditionalSenseCodeQualifier;
|
|
unsigned char FieldReplaceableUnitCode;
|
|
unsigned char SKSV;
|
|
unsigned short SenseKeySpecific;
|
|
} __attribute__ ((packed));
|
|
|
|
struct command_block_wrapper {
|
|
unsigned int signature;
|
|
unsigned int tag;
|
|
unsigned int data_transfer_length;
|
|
unsigned char flags;
|
|
unsigned char lun;
|
|
unsigned char command_length;
|
|
unsigned char command_block[16];
|
|
} __attribute__ ((packed));
|
|
|
|
struct command_status_wrapper {
|
|
unsigned int signature;
|
|
unsigned int tag;
|
|
unsigned int data_residue;
|
|
unsigned char status;
|
|
} __attribute__ ((packed));
|
|
|
|
struct capacity {
|
|
unsigned int block_count;
|
|
unsigned int block_size;
|
|
} __attribute__ ((packed));
|
|
|
|
struct format_capacity {
|
|
unsigned int following_length;
|
|
unsigned int block_count;
|
|
unsigned int block_size;
|
|
} __attribute__ ((packed));
|
|
|
|
/* the ARC USB controller can at most buffer 16KB unaligned data */
|
|
static unsigned char _transfer_buffer[16384*8] __attribute((aligned (4096)));
|
|
static unsigned char* transfer_buffer;
|
|
static struct inquiry_data _inquiry CACHEALIGN_ATTR;
|
|
static struct inquiry_data* inquiry;
|
|
static struct capacity _capacity_data CACHEALIGN_ATTR;
|
|
static struct capacity* capacity_data;
|
|
static struct format_capacity _format_capacity_data CACHEALIGN_ATTR;
|
|
static struct format_capacity* format_capacity_data;
|
|
static struct sense_data _sense_data CACHEALIGN_ATTR;
|
|
static struct sense_data *sense_data;
|
|
|
|
static struct {
|
|
unsigned int sector;
|
|
unsigned int count;
|
|
unsigned int tag;
|
|
unsigned int lun;
|
|
} current_cmd;
|
|
|
|
static void handle_scsi(struct command_block_wrapper* cbw);
|
|
static void send_csw(unsigned int tag, int status);
|
|
static void identify2inquiry(int lun);
|
|
|
|
static enum {
|
|
IDLE,
|
|
SENDING,
|
|
RECEIVING
|
|
} state = IDLE;
|
|
|
|
/* called by usb_code_init() */
|
|
void usb_storage_init(void)
|
|
{
|
|
inquiry = (void*)UNCACHED_ADDR(&_inquiry);
|
|
transfer_buffer = (void*)UNCACHED_ADDR(&_transfer_buffer);
|
|
capacity_data = (void*)UNCACHED_ADDR(&_capacity_data);
|
|
format_capacity_data = (void*)UNCACHED_ADDR(&_format_capacity_data);
|
|
sense_data = (void*)UNCACHED_ADDR(&_sense_data);
|
|
state = IDLE;
|
|
logf("usb_storage_init done");
|
|
}
|
|
|
|
/* called by usb_core_transfer_complete() */
|
|
void usb_storage_transfer_complete(int endpoint)
|
|
{
|
|
struct command_block_wrapper* cbw = (void*)transfer_buffer;
|
|
|
|
switch (endpoint) {
|
|
case EP_RX:
|
|
//logf("ums: %d bytes in", length);
|
|
if(state == RECEIVING)
|
|
{
|
|
int receive_count=usb_drv_get_last_transfer_length();
|
|
logf("scsi write %d %d", current_cmd.sector, current_cmd.count);
|
|
if(usb_drv_get_last_transfer_status()==0)
|
|
{
|
|
if((unsigned int)receive_count!=(SECTOR_SIZE*current_cmd.count))
|
|
{
|
|
logf("%d >= %d",SECTOR_SIZE*current_cmd.count,receive_count);
|
|
}
|
|
ata_write_sectors(IF_MV2(current_cmd.lun,)
|
|
current_cmd.sector, current_cmd.count,
|
|
transfer_buffer);
|
|
send_csw(current_cmd.tag, SCSI_STATUS_GOOD);
|
|
}
|
|
else
|
|
{
|
|
logf("Transfer failed %X",usb_drv_get_last_transfer_status());
|
|
send_csw(current_cmd.tag, SCSI_STATUS_CHECK_CONDITION);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
state = SENDING;
|
|
handle_scsi(cbw);
|
|
}
|
|
|
|
break;
|
|
|
|
case EP_TX:
|
|
//logf("ums: out complete");
|
|
if(state != IDLE)
|
|
{
|
|
/* re-prime endpoint. We only need room for commands */
|
|
state = IDLE;
|
|
usb_drv_recv(EP_RX, transfer_buffer, 1024);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* called by usb_core_control_request() */
|
|
bool usb_storage_control_request(struct usb_ctrlrequest* req)
|
|
{
|
|
/* note: interrupt context */
|
|
|
|
bool handled = false;
|
|
|
|
switch (req->bRequest) {
|
|
case USB_BULK_GET_MAX_LUN: {
|
|
static char maxlun = NUM_VOLUMES - 1;
|
|
logf("ums: getmaxlun");
|
|
usb_drv_send(EP_CONTROL, UNCACHED_ADDR(&maxlun), 1);
|
|
usb_drv_recv(EP_CONTROL, NULL, 0); /* ack */
|
|
handled = true;
|
|
break;
|
|
}
|
|
|
|
case USB_BULK_RESET_REQUEST:
|
|
logf("ums: bulk reset");
|
|
usb_drv_reset_endpoint(EP_RX, false);
|
|
usb_drv_reset_endpoint(EP_TX, true);
|
|
usb_drv_send(EP_CONTROL, NULL, 0); /* ack */
|
|
handled = true;
|
|
break;
|
|
|
|
case USB_REQ_SET_CONFIGURATION:
|
|
logf("ums: set config");
|
|
/* prime rx endpoint. We only need room for commands */
|
|
state = IDLE;
|
|
usb_drv_recv(EP_RX, transfer_buffer, 1024);
|
|
handled = true;
|
|
break;
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
|
|
static void handle_scsi(struct command_block_wrapper* cbw)
|
|
{
|
|
/* USB Mass Storage assumes LBA capability.
|
|
TODO: support 48-bit LBA */
|
|
|
|
unsigned int sectors_per_transfer=0;
|
|
unsigned int length = cbw->data_transfer_length;
|
|
unsigned int block_size;
|
|
unsigned char lun = cbw->lun;
|
|
unsigned int block_size_mult = 1;
|
|
#ifdef HAVE_HOTSWAP
|
|
tCardInfo* cinfo = card_get_info(lun);
|
|
block_size = cinfo->blocksize;
|
|
if(cinfo->initialized==1)
|
|
{
|
|
sectors_per_transfer=(sizeof _transfer_buffer/ block_size);
|
|
}
|
|
#else
|
|
block_size = SECTOR_SIZE;
|
|
sectors_per_transfer=(sizeof _transfer_buffer/ block_size);
|
|
#endif
|
|
|
|
#ifdef MAX_LOG_SECTOR_SIZE
|
|
block_size_mult = disk_sector_multiplier;
|
|
#endif
|
|
|
|
switch (cbw->command_block[0]) {
|
|
case SCSI_TEST_UNIT_READY:
|
|
logf("scsi test_unit_ready %d",lun);
|
|
#ifdef HAVE_HOTSWAP
|
|
if(cinfo->initialized==1)
|
|
send_csw(cbw->tag, SCSI_STATUS_GOOD);
|
|
else
|
|
send_csw(cbw->tag, SCSI_STATUS_FAIL);
|
|
#else
|
|
send_csw(cbw->tag, SCSI_STATUS_GOOD);
|
|
#endif
|
|
break;
|
|
|
|
case SCSI_INQUIRY:
|
|
logf("scsi inquiry %d",lun);
|
|
identify2inquiry(lun);
|
|
length = MIN(length, cbw->command_block[4]);
|
|
usb_drv_send(EP_TX, inquiry, MIN(sizeof _inquiry, length));
|
|
send_csw(cbw->tag, SCSI_STATUS_GOOD);
|
|
break;
|
|
|
|
case SCSI_REQUEST_SENSE: {
|
|
sense_data->ResponseCode=0x70;
|
|
sense_data->filemark_eom_ili_sensekey=2;
|
|
sense_data->Information=2;
|
|
sense_data->AdditionalSenseLength=10;
|
|
sense_data->CommandSpecificInformation=0;
|
|
sense_data->AdditionalSenseCode=0x3a;
|
|
sense_data->AdditionalSenseCodeQualifier=0;
|
|
sense_data->FieldReplaceableUnitCode=0;
|
|
sense_data->SKSV=0;
|
|
sense_data->SenseKeySpecific=0;
|
|
logf("scsi request_sense %d",lun);
|
|
usb_drv_send(EP_TX, sense_data,
|
|
sizeof(_sense_data));
|
|
send_csw(cbw->tag, SCSI_STATUS_GOOD);
|
|
break;
|
|
}
|
|
|
|
case SCSI_MODE_SENSE: {
|
|
static unsigned char sense_data[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
logf("scsi mode_sense %d",lun);
|
|
usb_drv_send(EP_TX, UNCACHED_ADDR(&sense_data),
|
|
MIN(sizeof sense_data, length));
|
|
send_csw(cbw->tag, SCSI_STATUS_GOOD);
|
|
break;
|
|
}
|
|
|
|
case SCSI_START_STOP_UNIT:
|
|
logf("scsi start_stop unit %d",lun);
|
|
send_csw(cbw->tag, SCSI_STATUS_GOOD);
|
|
break;
|
|
|
|
case SCSI_ALLOW_MEDIUM_REMOVAL:
|
|
logf("scsi allow_medium_removal %d",lun);
|
|
send_csw(cbw->tag, SCSI_STATUS_GOOD);
|
|
break;
|
|
|
|
case SCSI_READ_FORMAT_CAPACITY: {
|
|
logf("scsi read_format_capacity %d",lun);
|
|
format_capacity_data->following_length=htobe32(8);
|
|
#ifdef HAVE_HOTSWAP
|
|
/* Careful: "block count" actually means "number of last block" */
|
|
if(cinfo->initialized==1)
|
|
{
|
|
format_capacity_data->block_count = htobe32(cinfo->numblocks - 1);
|
|
format_capacity_data->block_size = htobe32(cinfo->blocksize);
|
|
}
|
|
else
|
|
{
|
|
format_capacity_data->block_count = htobe32(0);
|
|
format_capacity_data->block_size = htobe32(0);
|
|
}
|
|
#else
|
|
unsigned short* identify = ata_get_identify();
|
|
/* Careful: "block count" actually means "number of last block" */
|
|
format_capacity_data->block_count = htobe32((identify[61] << 16 | identify[60]) / block_size_mult - 1);
|
|
format_capacity_data->block_size = htobe32(block_size * block_size_mult);
|
|
#endif
|
|
format_capacity_data->block_size |= SCSI_FORMAT_CAPACITY_FORMATTED_MEDIA;
|
|
|
|
usb_drv_send(EP_TX, format_capacity_data,
|
|
MIN(sizeof _format_capacity_data, length));
|
|
send_csw(cbw->tag, SCSI_STATUS_GOOD);
|
|
break;
|
|
}
|
|
|
|
case SCSI_READ_CAPACITY: {
|
|
logf("scsi read_capacity %d",lun);
|
|
#ifdef HAVE_HOTSWAP
|
|
/* Careful: "block count" actually means "number of last block" */
|
|
if(cinfo->initialized==1)
|
|
{
|
|
capacity_data->block_count = htobe32(cinfo->numblocks - 1);
|
|
capacity_data->block_size = htobe32(cinfo->blocksize);
|
|
}
|
|
else
|
|
{
|
|
capacity_data->block_count = htobe32(0);
|
|
capacity_data->block_size = htobe32(0);
|
|
}
|
|
#else
|
|
unsigned short* identify = ata_get_identify();
|
|
/* Careful : "block count" actually means the number of the last block */
|
|
capacity_data->block_count = htobe32((identify[61] << 16 | identify[60]) / block_size_mult - 1);
|
|
capacity_data->block_size = htobe32(block_size * block_size_mult);
|
|
#endif
|
|
usb_drv_send(EP_TX, capacity_data,
|
|
MIN(sizeof _capacity_data, length));
|
|
send_csw(cbw->tag, SCSI_STATUS_GOOD);
|
|
break;
|
|
}
|
|
|
|
case SCSI_READ_10:
|
|
current_cmd.sector = block_size_mult *
|
|
(cbw->command_block[2] << 24 |
|
|
cbw->command_block[3] << 16 |
|
|
cbw->command_block[4] << 8 |
|
|
cbw->command_block[5] );
|
|
current_cmd.count = block_size_mult *
|
|
(cbw->command_block[7] << 16 |
|
|
cbw->command_block[8]);
|
|
current_cmd.tag = cbw->tag;
|
|
current_cmd.lun = cbw->lun;
|
|
|
|
//logf("scsi read %d %d", current_cmd.sector, current_cmd.count);
|
|
|
|
//logf("Asked for %d sectors",current_cmd.count);
|
|
if(current_cmd.count > sectors_per_transfer)
|
|
{
|
|
current_cmd.count = sectors_per_transfer;
|
|
}
|
|
//logf("Sending %d sectors",current_cmd.count);
|
|
|
|
if(current_cmd.count*block_size > sizeof(_transfer_buffer)) {
|
|
send_csw(current_cmd.tag, SCSI_STATUS_CHECK_CONDITION);
|
|
}
|
|
else {
|
|
ata_read_sectors(IF_MV2(lun,) current_cmd.sector,
|
|
current_cmd.count, transfer_buffer);
|
|
usb_drv_send(EP_TX, transfer_buffer,
|
|
current_cmd.count*block_size);
|
|
send_csw(current_cmd.tag, SCSI_STATUS_GOOD);
|
|
}
|
|
break;
|
|
|
|
case SCSI_WRITE_10:
|
|
//logf("scsi write10");
|
|
current_cmd.sector = block_size_mult *
|
|
(cbw->command_block[2] << 24 |
|
|
cbw->command_block[3] << 16 |
|
|
cbw->command_block[4] << 8 |
|
|
cbw->command_block[5] );
|
|
current_cmd.count = block_size_mult *
|
|
(cbw->command_block[7] << 16 |
|
|
cbw->command_block[8]);
|
|
current_cmd.tag = cbw->tag;
|
|
current_cmd.lun = cbw->lun;
|
|
/* expect data */
|
|
if(current_cmd.count*block_size > sizeof(_transfer_buffer)) {
|
|
send_csw(current_cmd.tag, SCSI_STATUS_CHECK_CONDITION);
|
|
}
|
|
else {
|
|
usb_drv_recv(EP_RX, transfer_buffer,
|
|
current_cmd.count*block_size);
|
|
state = RECEIVING;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
logf("scsi unknown cmd %x",cbw->command_block[0x0]);
|
|
usb_drv_stall(EP_TX, true);
|
|
send_csw(current_cmd.tag, SCSI_STATUS_GOOD);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void send_csw(unsigned int tag, int status)
|
|
{
|
|
static struct command_status_wrapper _csw;
|
|
struct command_status_wrapper* csw = UNCACHED_ADDR(&_csw);
|
|
csw->signature = CSW_SIGNATURE;
|
|
csw->tag = tag;
|
|
csw->data_residue = 0;
|
|
csw->status = status;
|
|
|
|
//logf("csw %x %x", csw->tag, csw->signature);
|
|
usb_drv_send(EP_TX, csw, sizeof _csw);
|
|
}
|
|
|
|
/* convert ATA IDENTIFY to SCSI INQUIRY */
|
|
static void identify2inquiry(int lun)
|
|
{
|
|
#ifdef HAVE_FLASH_STORAGE
|
|
if(lun==0)
|
|
{
|
|
memcpy(&inquiry->VendorId,"Rockbox ",8);
|
|
memcpy(&inquiry->ProductId,"Internal Storage",16);
|
|
memcpy(&inquiry->ProductRevisionLevel,"0.00",4);
|
|
}
|
|
else
|
|
{
|
|
memcpy(&inquiry->VendorId,"Rockbox ",8);
|
|
memcpy(&inquiry->ProductId,"SD Card Slot ",16);
|
|
memcpy(&inquiry->ProductRevisionLevel,"0.00",4);
|
|
}
|
|
#else
|
|
unsigned int i;
|
|
unsigned short* dest;
|
|
unsigned short* src;
|
|
unsigned short* identify = ata_get_identify();
|
|
(void)lun;
|
|
memset(inquiry, 0, sizeof _inquiry);
|
|
|
|
if (identify[82] & 4)
|
|
inquiry->DeviceTypeModifier = DEVICE_REMOVABLE;
|
|
|
|
/* ATA only has a 'model' field, so we copy the
|
|
first 8 bytes to 'vendor' and the rest to 'product' (they are
|
|
consecutive in the inquiry struct) */
|
|
src = (unsigned short*)&identify[27];
|
|
dest = (unsigned short*)&inquiry->VendorId;
|
|
for (i=0;i<12;i++)
|
|
dest[i] = htobe16(src[i]);
|
|
|
|
src = (unsigned short*)&identify[23];
|
|
dest = (unsigned short*)&inquiry->ProductRevisionLevel;
|
|
for (i=0;i<2;i++)
|
|
dest[i] = htobe16(src[i]);
|
|
#endif
|
|
|
|
inquiry->DeviceType = DIRECT_ACCESS_DEVICE;
|
|
inquiry->AdditionalLength = 0x1f;
|
|
inquiry->Versions = 3; /* ANSI SCSI level 2 */
|
|
inquiry->Format = 3; /* ANSI SCSI level 2 INQUIRY format */
|
|
|
|
#ifdef HAVE_HOTSWAP
|
|
inquiry->DeviceTypeModifier = DEVICE_REMOVABLE;
|
|
#endif
|
|
|
|
}
|
|
|