|
|
|
@ -0,0 +1,897 @@
|
|
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2020 Michael Brown <mbrown@fensystems.co.uk>.
|
|
|
|
|
*
|
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
|
* modify it under the terms of the GNU General Public License as
|
|
|
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful, but
|
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
* General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
|
* 02110-1301, USA.
|
|
|
|
|
*
|
|
|
|
|
* You can also choose to distribute this program under the terms of
|
|
|
|
|
* the Unmodified Binary Distribution Licence (as given in the file
|
|
|
|
|
* COPYING.UBDL), provided that you have satisfied its requirements.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|
|
|
|
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <ipxe/usb.h>
|
|
|
|
|
#include <ipxe/scsi.h>
|
|
|
|
|
#include <ipxe/xfer.h>
|
|
|
|
|
#include <ipxe/uri.h>
|
|
|
|
|
#include <ipxe/open.h>
|
|
|
|
|
#include "usbblk.h"
|
|
|
|
|
|
|
|
|
|
/** @file
|
|
|
|
|
*
|
|
|
|
|
* USB mass storage driver
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
static void usbblk_stop ( struct usbblk_device *usbblk, int rc );
|
|
|
|
|
|
|
|
|
|
/** List of USB block devices */
|
|
|
|
|
static LIST_HEAD ( usbblk_devices );
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* Endpoint management
|
|
|
|
|
*
|
|
|
|
|
******************************************************************************
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Open endpoints
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @ret rc Return status code
|
|
|
|
|
*/
|
|
|
|
|
static int usbblk_open ( struct usbblk_device *usbblk ) {
|
|
|
|
|
struct usb_device *usb = usbblk->func->usb;
|
|
|
|
|
unsigned int interface = usbblk->func->interface[0];
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
/* Sanity checks */
|
|
|
|
|
assert ( ! usbblk->in.open );
|
|
|
|
|
assert ( ! usbblk->out.open );
|
|
|
|
|
|
|
|
|
|
/* Issue reset */
|
|
|
|
|
if ( ( rc = usb_control ( usb, USBBLK_RESET, 0, interface,
|
|
|
|
|
NULL, 0 ) ) != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s could not issue reset: %s\n",
|
|
|
|
|
usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
goto err_reset;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Open bulk OUT endpoint */
|
|
|
|
|
if ( ( rc = usb_endpoint_open ( &usbblk->out ) ) != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s could not open bulk OUT: %s\n",
|
|
|
|
|
usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
goto err_open_out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Clear any bulk OUT halt condition */
|
|
|
|
|
if ( ( rc = usb_endpoint_clear_halt ( &usbblk->out ) ) != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s could not reset bulk OUT: %s\n",
|
|
|
|
|
usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
goto err_clear_out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Open bulk IN endpoint */
|
|
|
|
|
if ( ( rc = usb_endpoint_open ( &usbblk->in ) ) != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s could not open bulk IN: %s\n",
|
|
|
|
|
usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
goto err_open_in;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Clear any bulk IN halt condition */
|
|
|
|
|
if ( ( rc = usb_endpoint_clear_halt ( &usbblk->in ) ) != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s could not reset bulk IN: %s\n",
|
|
|
|
|
usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
goto err_clear_in;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
err_clear_in:
|
|
|
|
|
usb_endpoint_close ( &usbblk->in );
|
|
|
|
|
err_open_in:
|
|
|
|
|
err_clear_out:
|
|
|
|
|
usb_endpoint_close ( &usbblk->out );
|
|
|
|
|
err_open_out:
|
|
|
|
|
err_reset:
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Close endpoints
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
*/
|
|
|
|
|
static void usbblk_close ( struct usbblk_device *usbblk ) {
|
|
|
|
|
|
|
|
|
|
/* Close bulk OUT endpoint */
|
|
|
|
|
if ( usbblk->out.open )
|
|
|
|
|
usb_endpoint_close ( &usbblk->out );
|
|
|
|
|
|
|
|
|
|
/* Close bulk IN endpoint */
|
|
|
|
|
if ( usbblk->in.open )
|
|
|
|
|
usb_endpoint_close ( &usbblk->in );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* Bulk OUT endpoint
|
|
|
|
|
*
|
|
|
|
|
******************************************************************************
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Issue bulk OUT command
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @ret rc Return status code
|
|
|
|
|
*/
|
|
|
|
|
static int usbblk_out_command ( struct usbblk_device *usbblk ) {
|
|
|
|
|
struct usbblk_command *cmd = &usbblk->cmd;
|
|
|
|
|
struct usbblk_command_wrapper *wrapper;
|
|
|
|
|
struct io_buffer *iobuf;
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
/* Sanity checks */
|
|
|
|
|
assert ( cmd->tag );
|
|
|
|
|
assert ( ! ( cmd->scsi.data_in_len && cmd->scsi.data_out_len ) );
|
|
|
|
|
|
|
|
|
|
/* Allocate I/O buffer */
|
|
|
|
|
iobuf = alloc_iob ( sizeof ( *wrapper ) );
|
|
|
|
|
if ( ! iobuf ) {
|
|
|
|
|
rc = -ENOMEM;
|
|
|
|
|
goto err_alloc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Populate command */
|
|
|
|
|
wrapper = iob_put ( iobuf, sizeof ( *wrapper ) );
|
|
|
|
|
memset ( wrapper, 0, sizeof ( *wrapper ) );
|
|
|
|
|
wrapper->signature = cpu_to_le32 ( USBBLK_COMMAND_SIGNATURE );
|
|
|
|
|
wrapper->tag = cmd->tag; /* non-endian */
|
|
|
|
|
if ( cmd->scsi.data_out_len ) {
|
|
|
|
|
wrapper->len = cpu_to_le32 ( cmd->scsi.data_out_len );
|
|
|
|
|
} else {
|
|
|
|
|
wrapper->len = cpu_to_le32 ( cmd->scsi.data_in_len );
|
|
|
|
|
wrapper->flags = USB_DIR_IN;
|
|
|
|
|
}
|
|
|
|
|
wrapper->lun = ntohs ( cmd->scsi.lun.u16[0] );
|
|
|
|
|
wrapper->cblen = sizeof ( wrapper->cb );
|
|
|
|
|
memcpy ( wrapper->cb, &cmd->scsi.cdb, sizeof ( wrapper->cb ) );
|
|
|
|
|
|
|
|
|
|
/* Issue command */
|
|
|
|
|
if ( ( rc = usb_stream ( &usbblk->out, iobuf, 0 ) ) != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s bulk OUT could not issue command: "
|
|
|
|
|
"%s\n", usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
goto err_stream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
err_stream:
|
|
|
|
|
free_iob ( iobuf );
|
|
|
|
|
err_alloc:
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Send bulk OUT data block
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @ret rc Return status code
|
|
|
|
|
*/
|
|
|
|
|
static int usbblk_out_data ( struct usbblk_device *usbblk ) {
|
|
|
|
|
struct usbblk_command *cmd = &usbblk->cmd;
|
|
|
|
|
struct io_buffer *iobuf;
|
|
|
|
|
size_t len;
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
/* Calculate length */
|
|
|
|
|
assert ( cmd->tag );
|
|
|
|
|
assert ( cmd->scsi.data_out != UNULL );
|
|
|
|
|
assert ( cmd->offset < cmd->scsi.data_out_len );
|
|
|
|
|
len = ( cmd->scsi.data_out_len - cmd->offset );
|
|
|
|
|
if ( len > USBBLK_MAX_LEN )
|
|
|
|
|
len = USBBLK_MAX_LEN;
|
|
|
|
|
assert ( ( len % usbblk->out.mtu ) == 0 );
|
|
|
|
|
|
|
|
|
|
/* Allocate I/O buffer */
|
|
|
|
|
iobuf = alloc_iob ( len );
|
|
|
|
|
if ( ! iobuf ) {
|
|
|
|
|
rc = -ENOMEM;
|
|
|
|
|
goto err_alloc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Populate I/O buffer */
|
|
|
|
|
copy_from_user ( iob_put ( iobuf, len ), cmd->scsi.data_out,
|
|
|
|
|
cmd->offset, len );
|
|
|
|
|
|
|
|
|
|
/* Send data */
|
|
|
|
|
if ( ( rc = usb_stream ( &usbblk->out, iobuf, 0 ) ) != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s bulk OUT could not send data: %s\n",
|
|
|
|
|
usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
goto err_stream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Consume data */
|
|
|
|
|
cmd->offset += len;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
err_stream:
|
|
|
|
|
free_iob ( iobuf );
|
|
|
|
|
err_alloc:
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Refill bulk OUT endpoint
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @ret rc Return status code
|
|
|
|
|
*/
|
|
|
|
|
static int usbblk_out_refill ( struct usbblk_device *usbblk ) {
|
|
|
|
|
struct usbblk_command *cmd = &usbblk->cmd;
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
/* Sanity checks */
|
|
|
|
|
assert ( cmd->tag );
|
|
|
|
|
|
|
|
|
|
/* Refill endpoint */
|
|
|
|
|
while ( ( cmd->offset < cmd->scsi.data_out_len ) &&
|
|
|
|
|
( usbblk->out.fill < USBBLK_MAX_FILL ) ) {
|
|
|
|
|
if ( ( rc = usbblk_out_data ( usbblk ) ) != 0 )
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Complete bulk OUT transfer
|
|
|
|
|
*
|
|
|
|
|
* @v ep USB endpoint
|
|
|
|
|
* @v iobuf I/O buffer
|
|
|
|
|
* @v rc Completion status code
|
|
|
|
|
*/
|
|
|
|
|
static void usbblk_out_complete ( struct usb_endpoint *ep,
|
|
|
|
|
struct io_buffer *iobuf, int rc ) {
|
|
|
|
|
struct usbblk_device *usbblk =
|
|
|
|
|
container_of ( ep, struct usbblk_device, out );
|
|
|
|
|
struct usbblk_command *cmd = &usbblk->cmd;
|
|
|
|
|
|
|
|
|
|
/* Ignore cancellations after closing endpoint */
|
|
|
|
|
if ( ! ep->open )
|
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
|
|
/* Sanity check */
|
|
|
|
|
assert ( cmd->tag );
|
|
|
|
|
|
|
|
|
|
/* Check for failures */
|
|
|
|
|
if ( rc != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s bulk OUT failed: %s\n",
|
|
|
|
|
usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Trigger refill process, if applicable */
|
|
|
|
|
if ( cmd->offset < cmd->scsi.data_out_len )
|
|
|
|
|
process_add ( &usbblk->process );
|
|
|
|
|
|
|
|
|
|
drop:
|
|
|
|
|
/* Free I/O buffer */
|
|
|
|
|
free_iob ( iobuf );
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
free_iob ( iobuf );
|
|
|
|
|
usbblk_stop ( usbblk, rc );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Bulk OUT endpoint operations */
|
|
|
|
|
static struct usb_endpoint_driver_operations usbblk_out_operations = {
|
|
|
|
|
.complete = usbblk_out_complete,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* Bulk IN endpoint
|
|
|
|
|
*
|
|
|
|
|
******************************************************************************
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle bulk IN data block
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @v data Data block
|
|
|
|
|
* @v len Length of data
|
|
|
|
|
* @ret rc Return status code
|
|
|
|
|
*/
|
|
|
|
|
static int usbblk_in_data ( struct usbblk_device *usbblk, const void *data,
|
|
|
|
|
size_t len ) {
|
|
|
|
|
struct usbblk_command *cmd = &usbblk->cmd;
|
|
|
|
|
|
|
|
|
|
/* Sanity checks */
|
|
|
|
|
assert ( cmd->tag );
|
|
|
|
|
assert ( cmd->scsi.data_in != UNULL );
|
|
|
|
|
assert ( cmd->offset <= cmd->scsi.data_in_len );
|
|
|
|
|
assert ( len <= ( cmd->scsi.data_in_len - cmd->offset ) );
|
|
|
|
|
|
|
|
|
|
/* Store data */
|
|
|
|
|
copy_to_user ( cmd->scsi.data_in, cmd->offset, data, len );
|
|
|
|
|
cmd->offset += len;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle bulk IN status
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @v data Status data
|
|
|
|
|
* @v len Length of status data
|
|
|
|
|
* @ret rc Return status code
|
|
|
|
|
*/
|
|
|
|
|
static int usbblk_in_status ( struct usbblk_device *usbblk, const void *data,
|
|
|
|
|
size_t len ) {
|
|
|
|
|
struct usbblk_command *cmd = &usbblk->cmd;
|
|
|
|
|
const struct usbblk_status_wrapper *stat;
|
|
|
|
|
|
|
|
|
|
/* Sanity checks */
|
|
|
|
|
assert ( cmd->tag );
|
|
|
|
|
|
|
|
|
|
/* Validate length */
|
|
|
|
|
if ( len < sizeof ( *stat ) ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s bulk IN malformed status:\n",
|
|
|
|
|
usbblk->func->name );
|
|
|
|
|
DBGC_HDA ( usbblk, 0, data, len );
|
|
|
|
|
return -EIO;
|
|
|
|
|
}
|
|
|
|
|
stat = data;
|
|
|
|
|
|
|
|
|
|
/* Validate signature */
|
|
|
|
|
if ( stat->signature != cpu_to_le32 ( USBBLK_STATUS_SIGNATURE ) ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s bulk IN invalid signature %08x:\n",
|
|
|
|
|
usbblk->func->name, le32_to_cpu ( stat->signature ) );
|
|
|
|
|
DBGC_HDA ( usbblk, 0, stat, sizeof ( *stat ) );
|
|
|
|
|
return -EIO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Validate tag */
|
|
|
|
|
if ( stat->tag != cmd->tag ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s bulk IN tag mismatch (got %08x, "
|
|
|
|
|
"expected %08x):\n",
|
|
|
|
|
usbblk->func->name, stat->tag, cmd->tag );
|
|
|
|
|
DBGC_HDA ( usbblk, 0, stat, sizeof ( *stat ) );
|
|
|
|
|
return -EIO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check status */
|
|
|
|
|
if ( stat->status ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s bulk IN status %02x:\n",
|
|
|
|
|
usbblk->func->name, stat->status );
|
|
|
|
|
DBGC_HDA ( usbblk, 0, stat, sizeof ( *stat ) );
|
|
|
|
|
return -EIO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check for residual data */
|
|
|
|
|
if ( stat->residue ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s bulk IN residue %#x:\n",
|
|
|
|
|
usbblk->func->name, le32_to_cpu ( stat->residue ) );
|
|
|
|
|
return -EIO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Mark command as complete */
|
|
|
|
|
usbblk_stop ( usbblk, 0 );
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Refill bulk IN endpoint
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @ret rc Return status code
|
|
|
|
|
*/
|
|
|
|
|
static int usbblk_in_refill ( struct usbblk_device *usbblk ) {
|
|
|
|
|
struct usbblk_command *cmd = &usbblk->cmd;
|
|
|
|
|
struct usbblk_status_wrapper *stat;
|
|
|
|
|
size_t remaining;
|
|
|
|
|
unsigned int max;
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
/* Sanity checks */
|
|
|
|
|
assert ( cmd->tag );
|
|
|
|
|
|
|
|
|
|
/* Calculate maximum required refill */
|
|
|
|
|
remaining = sizeof ( *stat );
|
|
|
|
|
if ( cmd->scsi.data_in_len ) {
|
|
|
|
|
assert ( cmd->offset <= cmd->scsi.data_in_len );
|
|
|
|
|
remaining += ( cmd->scsi.data_in_len - cmd->offset );
|
|
|
|
|
}
|
|
|
|
|
max = ( ( remaining + USBBLK_MAX_LEN - 1 ) / USBBLK_MAX_LEN );
|
|
|
|
|
|
|
|
|
|
/* Refill bulk IN endpoint */
|
|
|
|
|
if ( ( rc = usb_refill_limit ( &usbblk->in, max ) ) != 0 )
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Complete bulk IN transfer
|
|
|
|
|
*
|
|
|
|
|
* @v ep USB endpoint
|
|
|
|
|
* @v iobuf I/O buffer
|
|
|
|
|
* @v rc Completion status code
|
|
|
|
|
*/
|
|
|
|
|
static void usbblk_in_complete ( struct usb_endpoint *ep,
|
|
|
|
|
struct io_buffer *iobuf, int rc ) {
|
|
|
|
|
struct usbblk_device *usbblk =
|
|
|
|
|
container_of ( ep, struct usbblk_device, in );
|
|
|
|
|
struct usbblk_command *cmd = &usbblk->cmd;
|
|
|
|
|
size_t remaining;
|
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
|
|
/* Ignore cancellations after closing endpoint */
|
|
|
|
|
if ( ! ep->open )
|
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
|
|
/* Sanity check */
|
|
|
|
|
assert ( cmd->tag );
|
|
|
|
|
|
|
|
|
|
/* Handle errors */
|
|
|
|
|
if ( rc != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s bulk IN failed: %s\n",
|
|
|
|
|
usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Trigger refill process */
|
|
|
|
|
process_add ( &usbblk->process );
|
|
|
|
|
|
|
|
|
|
/* Handle data portion, if any */
|
|
|
|
|
if ( cmd->scsi.data_in_len ) {
|
|
|
|
|
assert ( cmd->offset <= cmd->scsi.data_in_len );
|
|
|
|
|
remaining = ( cmd->scsi.data_in_len - cmd->offset );
|
|
|
|
|
len = iob_len ( iobuf );
|
|
|
|
|
if ( len > remaining )
|
|
|
|
|
len = remaining;
|
|
|
|
|
if ( len ) {
|
|
|
|
|
if ( ( rc = usbblk_in_data ( usbblk, iobuf->data,
|
|
|
|
|
len ) ) != 0 )
|
|
|
|
|
goto err;
|
|
|
|
|
iob_pull ( iobuf, len );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Handle status portion, if any */
|
|
|
|
|
len = iob_len ( iobuf );
|
|
|
|
|
if ( len ) {
|
|
|
|
|
if ( ( rc = usbblk_in_status ( usbblk, iobuf->data,
|
|
|
|
|
len ) ) != 0 )
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drop:
|
|
|
|
|
/* Free I/O buffer */
|
|
|
|
|
free_iob ( iobuf );
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
free_iob ( iobuf );
|
|
|
|
|
usbblk_stop ( usbblk, rc );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Bulk IN endpoint operations */
|
|
|
|
|
static struct usb_endpoint_driver_operations usbblk_in_operations = {
|
|
|
|
|
.complete = usbblk_in_complete,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* Refill process
|
|
|
|
|
*
|
|
|
|
|
******************************************************************************
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Refill endpoints
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
*/
|
|
|
|
|
static void usbblk_step ( struct usbblk_device *usbblk ) {
|
|
|
|
|
|
|
|
|
|
/* Refill bulk OUT endpoint */
|
|
|
|
|
usbblk_out_refill ( usbblk );
|
|
|
|
|
|
|
|
|
|
/* Refill bulk IN endpoint */
|
|
|
|
|
usbblk_in_refill ( usbblk );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Refill process descriptor */
|
|
|
|
|
static struct process_descriptor usbblk_process_desc =
|
|
|
|
|
PROC_DESC ( struct usbblk_device, process, usbblk_step );
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* SCSI command management
|
|
|
|
|
*
|
|
|
|
|
******************************************************************************
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/** Next command tag */
|
|
|
|
|
static uint16_t usbblk_tag;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stop SCSI command
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @v rc Reason for stop
|
|
|
|
|
*/
|
|
|
|
|
static void usbblk_stop ( struct usbblk_device *usbblk, int rc ) {
|
|
|
|
|
|
|
|
|
|
/* Stop process */
|
|
|
|
|
process_del ( &usbblk->process );
|
|
|
|
|
|
|
|
|
|
/* Reset command */
|
|
|
|
|
memset ( &usbblk->cmd, 0, sizeof ( usbblk->cmd ) );
|
|
|
|
|
|
|
|
|
|
/* Close endpoints if an error occurred */
|
|
|
|
|
if ( rc != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s closing for error recovery\n",
|
|
|
|
|
usbblk->func->name );
|
|
|
|
|
usbblk_close ( usbblk );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Terminate command */
|
|
|
|
|
intf_restart ( &usbblk->data, rc );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Start new SCSI command
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @v scsicmd SCSI command
|
|
|
|
|
* @ret rc Return status code
|
|
|
|
|
*/
|
|
|
|
|
static int usbblk_start ( struct usbblk_device *usbblk,
|
|
|
|
|
struct scsi_cmd *scsicmd ) {
|
|
|
|
|
struct usbblk_command *cmd = &usbblk->cmd;
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
/* Fail if command is in progress */
|
|
|
|
|
if ( cmd->tag ) {
|
|
|
|
|
rc = -EBUSY;
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s cannot support multiple commands\n",
|
|
|
|
|
usbblk->func->name );
|
|
|
|
|
goto err_busy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Refuse bidirectional commands */
|
|
|
|
|
if ( scsicmd->data_in_len && scsicmd->data_out_len ) {
|
|
|
|
|
rc = -EOPNOTSUPP;
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s cannot support bidirectional "
|
|
|
|
|
"commands\n", usbblk->func->name );
|
|
|
|
|
goto err_bidirectional;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Sanity checks */
|
|
|
|
|
assert ( ! process_running ( &usbblk->process ) );
|
|
|
|
|
assert ( cmd->offset == 0 );
|
|
|
|
|
|
|
|
|
|
/* Initialise command */
|
|
|
|
|
memcpy ( &cmd->scsi, scsicmd, sizeof ( cmd->scsi ) );
|
|
|
|
|
cmd->tag = ( USBBLK_TAG_MAGIC | ++usbblk_tag );
|
|
|
|
|
|
|
|
|
|
/* Issue bulk OUT command */
|
|
|
|
|
if ( ( rc = usbblk_out_command ( usbblk ) ) != 0 )
|
|
|
|
|
goto err_command;
|
|
|
|
|
|
|
|
|
|
/* Start refill process */
|
|
|
|
|
process_add ( &usbblk->process );
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
err_command:
|
|
|
|
|
memset ( &usbblk->cmd, 0, sizeof ( usbblk->cmd ) );
|
|
|
|
|
err_bidirectional:
|
|
|
|
|
err_busy:
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* SCSI interfaces
|
|
|
|
|
*
|
|
|
|
|
******************************************************************************
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/** SCSI data interface operations */
|
|
|
|
|
static struct interface_operation usbblk_data_operations[] = {
|
|
|
|
|
INTF_OP ( intf_close, struct usbblk_device *, usbblk_stop ),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** SCSI data interface descriptor */
|
|
|
|
|
static struct interface_descriptor usbblk_data_desc =
|
|
|
|
|
INTF_DESC ( struct usbblk_device, data, usbblk_data_operations );
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check SCSI command flow-control window
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @ret len Length of window
|
|
|
|
|
*/
|
|
|
|
|
static size_t usbblk_scsi_window ( struct usbblk_device *usbblk ) {
|
|
|
|
|
struct usbblk_command *cmd = &usbblk->cmd;
|
|
|
|
|
|
|
|
|
|
/* Allow a single command if no command is currently in progress */
|
|
|
|
|
return ( cmd->tag ? 0 : 1 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Issue SCSI command
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @v data SCSI data interface
|
|
|
|
|
* @v scsicmd SCSI command
|
|
|
|
|
* @ret tag Command tag, or negative error
|
|
|
|
|
*/
|
|
|
|
|
static int usbblk_scsi_command ( struct usbblk_device *usbblk,
|
|
|
|
|
struct interface *data,
|
|
|
|
|
struct scsi_cmd *scsicmd ) {
|
|
|
|
|
struct usbblk_command *cmd = &usbblk->cmd;
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
/* (Re)open endpoints if needed */
|
|
|
|
|
if ( ( ! usbblk->in.open ) && ( ( rc = usbblk_open ( usbblk ) ) != 0 ) )
|
|
|
|
|
goto err_open;
|
|
|
|
|
|
|
|
|
|
/* Start new command */
|
|
|
|
|
if ( ( rc = usbblk_start ( usbblk, scsicmd ) ) != 0 )
|
|
|
|
|
goto err_start;
|
|
|
|
|
|
|
|
|
|
/* Attach to parent interface and return */
|
|
|
|
|
intf_plug_plug ( &usbblk->data, data );
|
|
|
|
|
return cmd->tag;
|
|
|
|
|
|
|
|
|
|
usbblk_stop ( usbblk, rc );
|
|
|
|
|
err_start:
|
|
|
|
|
usbblk_close ( usbblk );
|
|
|
|
|
err_open:
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Close SCSI interface
|
|
|
|
|
*
|
|
|
|
|
* @v usbblk USB block device
|
|
|
|
|
* @v rc Reason for close
|
|
|
|
|
*/
|
|
|
|
|
static void usbblk_scsi_close ( struct usbblk_device *usbblk, int rc ) {
|
|
|
|
|
|
|
|
|
|
/* Restart interfaces */
|
|
|
|
|
intfs_restart ( rc, &usbblk->scsi, &usbblk->data, NULL );
|
|
|
|
|
|
|
|
|
|
/* Stop any in-progress command */
|
|
|
|
|
usbblk_stop ( usbblk, rc );
|
|
|
|
|
|
|
|
|
|
/* Close endpoints */
|
|
|
|
|
usbblk_close ( usbblk );
|
|
|
|
|
|
|
|
|
|
/* Flag as closed */
|
|
|
|
|
usbblk->opened = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** SCSI command interface operations */
|
|
|
|
|
static struct interface_operation usbblk_scsi_operations[] = {
|
|
|
|
|
INTF_OP ( scsi_command, struct usbblk_device *, usbblk_scsi_command ),
|
|
|
|
|
INTF_OP ( xfer_window, struct usbblk_device *, usbblk_scsi_window ),
|
|
|
|
|
INTF_OP ( intf_close, struct usbblk_device *, usbblk_scsi_close ),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** SCSI command interface descriptor */
|
|
|
|
|
static struct interface_descriptor usbblk_scsi_desc =
|
|
|
|
|
INTF_DESC ( struct usbblk_device, scsi, usbblk_scsi_operations );
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* SAN device interface
|
|
|
|
|
*
|
|
|
|
|
******************************************************************************
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Find USB block device
|
|
|
|
|
*
|
|
|
|
|
* @v name USB block device name
|
|
|
|
|
* @ret usbblk USB block device, or NULL
|
|
|
|
|
*/
|
|
|
|
|
static struct usbblk_device * usbblk_find ( const char *name ) {
|
|
|
|
|
struct usbblk_device *usbblk;
|
|
|
|
|
|
|
|
|
|
/* Look for matching device */
|
|
|
|
|
list_for_each_entry ( usbblk, &usbblk_devices, list ) {
|
|
|
|
|
if ( strcmp ( usbblk->func->name, name ) == 0 )
|
|
|
|
|
return usbblk;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Open USB block device URI
|
|
|
|
|
*
|
|
|
|
|
* @v parent Parent interface
|
|
|
|
|
* @v uri URI
|
|
|
|
|
* @ret rc Return status code
|
|
|
|
|
*/
|
|
|
|
|
static int usbblk_open_uri ( struct interface *parent, struct uri *uri ) {
|
|
|
|
|
static struct scsi_lun lun;
|
|
|
|
|
struct usbblk_device *usbblk;
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
/* Sanity check */
|
|
|
|
|
if ( ! uri->opaque )
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
/* Find matching device */
|
|
|
|
|
usbblk = usbblk_find ( uri->opaque );
|
|
|
|
|
if ( ! usbblk )
|
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
|
|
/* Fail if device is already open */
|
|
|
|
|
if ( usbblk->opened )
|
|
|
|
|
return -EBUSY;
|
|
|
|
|
|
|
|
|
|
/* Open SCSI device */
|
|
|
|
|
if ( ( rc = scsi_open ( parent, &usbblk->scsi, &lun ) ) != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s could not open SCSI device: %s\n",
|
|
|
|
|
usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Mark as opened */
|
|
|
|
|
usbblk->opened = 1;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** USB block device URI opener */
|
|
|
|
|
struct uri_opener usbblk_uri_opener __uri_opener = {
|
|
|
|
|
.scheme = "usb",
|
|
|
|
|
.open = usbblk_open_uri,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
*
|
|
|
|
|
* USB interface
|
|
|
|
|
*
|
|
|
|
|
******************************************************************************
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Probe device
|
|
|
|
|
*
|
|
|
|
|
* @v func USB function
|
|
|
|
|
* @v config Configuration descriptor
|
|
|
|
|
* @ret rc Return status code
|
|
|
|
|
*/
|
|
|
|
|
static int usbblk_probe ( struct usb_function *func,
|
|
|
|
|
struct usb_configuration_descriptor *config ) {
|
|
|
|
|
struct usb_device *usb = func->usb;
|
|
|
|
|
struct usbblk_device *usbblk;
|
|
|
|
|
struct usb_interface_descriptor *desc;
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
/* Allocate and initialise structure */
|
|
|
|
|
usbblk = zalloc ( sizeof ( *usbblk ) );
|
|
|
|
|
if ( ! usbblk ) {
|
|
|
|
|
rc = -ENOMEM;
|
|
|
|
|
goto err_alloc;
|
|
|
|
|
}
|
|
|
|
|
usbblk->func = func;
|
|
|
|
|
usb_endpoint_init ( &usbblk->out, usb, &usbblk_out_operations );
|
|
|
|
|
usb_endpoint_init ( &usbblk->in, usb, &usbblk_in_operations );
|
|
|
|
|
usb_refill_init ( &usbblk->in, 0, USBBLK_MAX_LEN, USBBLK_MAX_FILL );
|
|
|
|
|
intf_init ( &usbblk->scsi, &usbblk_scsi_desc, &usbblk->refcnt );
|
|
|
|
|
intf_init ( &usbblk->data, &usbblk_data_desc, &usbblk->refcnt );
|
|
|
|
|
process_init_stopped ( &usbblk->process, &usbblk_process_desc,
|
|
|
|
|
&usbblk->refcnt );
|
|
|
|
|
|
|
|
|
|
/* Locate interface descriptor */
|
|
|
|
|
desc = usb_interface_descriptor ( config, func->interface[0], 0 );
|
|
|
|
|
if ( ! desc ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s missing interface descriptor\n",
|
|
|
|
|
usbblk->func->name );
|
|
|
|
|
rc = -ENOENT;
|
|
|
|
|
goto err_desc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Describe endpoints */
|
|
|
|
|
if ( ( rc = usb_endpoint_described ( &usbblk->out, config, desc,
|
|
|
|
|
USB_BULK_OUT, 0 ) ) != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s could not describe bulk OUT: %s\n",
|
|
|
|
|
usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
goto err_out;
|
|
|
|
|
}
|
|
|
|
|
if ( ( rc = usb_endpoint_described ( &usbblk->in, config, desc,
|
|
|
|
|
USB_BULK_IN, 0 ) ) != 0 ) {
|
|
|
|
|
DBGC ( usbblk, "USBBLK %s could not describe bulk IN: %s\n",
|
|
|
|
|
usbblk->func->name, strerror ( rc ) );
|
|
|
|
|
goto err_in;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Add to list of devices */
|
|
|
|
|
list_add_tail ( &usbblk->list, &usbblk_devices );
|
|
|
|
|
|
|
|
|
|
usb_func_set_drvdata ( func, usbblk );
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
err_in:
|
|
|
|
|
err_out:
|
|
|
|
|
err_desc:
|
|
|
|
|
ref_put ( &usbblk->refcnt );
|
|
|
|
|
err_alloc:
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove device
|
|
|
|
|
*
|
|
|
|
|
* @v func USB function
|
|
|
|
|
*/
|
|
|
|
|
static void usbblk_remove ( struct usb_function *func ) {
|
|
|
|
|
struct usbblk_device *usbblk = usb_func_get_drvdata ( func );
|
|
|
|
|
|
|
|
|
|
/* Remove from list of devices */
|
|
|
|
|
list_del ( &usbblk->list );
|
|
|
|
|
|
|
|
|
|
/* Close all interfaces */
|
|
|
|
|
usbblk_scsi_close ( usbblk, -ENODEV );
|
|
|
|
|
|
|
|
|
|
/* Shut down interfaces */
|
|
|
|
|
intfs_shutdown ( -ENODEV, &usbblk->scsi, &usbblk->data, NULL );
|
|
|
|
|
|
|
|
|
|
/* Drop reference */
|
|
|
|
|
ref_put ( &usbblk->refcnt );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Mass storage class device IDs */
|
|
|
|
|
static struct usb_device_id usbblk_ids[] = {
|
|
|
|
|
{
|
|
|
|
|
.name = "usbblk",
|
|
|
|
|
.vendor = USB_ANY_ID,
|
|
|
|
|
.product = USB_ANY_ID,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** Mass storage driver */
|
|
|
|
|
struct usb_driver usbblk_driver __usb_driver = {
|
|
|
|
|
.ids = usbblk_ids,
|
|
|
|
|
.id_count = ( sizeof ( usbblk_ids ) / sizeof ( usbblk_ids[0] ) ),
|
|
|
|
|
.class = USB_CLASS_ID ( USB_CLASS_MSC, USB_SUBCLASS_MSC_SCSI,
|
|
|
|
|
USB_PROTOCOL_MSC_BULK ),
|
|
|
|
|
.score = USB_SCORE_NORMAL,
|
|
|
|
|
.probe = usbblk_probe,
|
|
|
|
|
.remove = usbblk_remove,
|
|
|
|
|
};
|