summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Brown2020-10-12 16:31:49 +0200
committerMichael Brown2020-10-13 16:56:38 +0200
commit6d680bdec59b103f84ee922ec66ae5219c939938 (patch)
tree161080c418e8c55c34f6e01597f96feaf1d95fde
parent[usb] Move usbio driver to end of USB driver list (diff)
downloadipxe-6d680bdec59b103f84ee922ec66ae5219c939938.tar.gz
ipxe-6d680bdec59b103f84ee922ec66ae5219c939938.tar.xz
ipxe-6d680bdec59b103f84ee922ec66ae5219c939938.zip
[usbblk] Add support for USB mass storage devices
Some UEFI BIOSes (observed with at least the Insyde UEFI BIOS on a Microsoft Surface Go) provide a very broken version of the UsbMassStorageDxe driver that is incapable of binding to the standard EFI_USB_IO_PROTOCOL instances and instead relies on an undocumented proprietary protocol (with GUID c965c76a-d71e-4e66-ab06-c6230d528425) installed by the platform's custom version of UsbCoreDxe. The upshot is that USB mass storage devices become inaccessible once iPXE's native USB host controller drivers are loaded. One possible workaround is to load a known working version of UsbMassStorageDxe (e.g. from the EDK2 tree): this driver will correctly bind to the standard EFI_USB_IO_PROTOCOL instances exposed by iPXE. This workaround is ugly in practice, since it involves embedding UsbMassStorageDxe.efi into the iPXE binary and including an embedded script to perform the required "chain UsbMassStorageDxe.efi". Provide a native USB mass storage driver for iPXE, allowing USB mass storage devices to be exposed as iPXE SAN devices. Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r--src/config/config_usb.c3
-rw-r--r--src/config/defaults/efi.h1
-rw-r--r--src/config/defaults/pcbios.h1
-rw-r--r--src/config/usb.h1
-rw-r--r--src/drivers/usb/usbblk.c897
-rw-r--r--src/drivers/usb/usbblk.h121
-rw-r--r--src/include/ipxe/errfile.h1
7 files changed, 1025 insertions, 0 deletions
diff --git a/src/config/config_usb.c b/src/config/config_usb.c
index 17296d27..b679aeb2 100644
--- a/src/config/config_usb.c
+++ b/src/config/config_usb.c
@@ -53,6 +53,9 @@ REQUIRE_OBJECT ( usbio );
#ifdef USB_KEYBOARD
REQUIRE_OBJECT ( usbkbd );
#endif
+#ifdef USB_BLOCK
+REQUIRE_OBJECT ( usbblk );
+#endif
/*
* Drag in USB external interfaces
diff --git a/src/config/defaults/efi.h b/src/config/defaults/efi.h
index e707dfa6..4ae6a316 100644
--- a/src/config/defaults/efi.h
+++ b/src/config/defaults/efi.h
@@ -39,6 +39,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define USB_HCD_EHCI /* EHCI USB host controller */
#define USB_HCD_UHCI /* UHCI USB host controller */
#define USB_EFI /* Provide EFI_USB_IO_PROTOCOL interface */
+#define USB_BLOCK /* USB block devices */
#define REBOOT_CMD /* Reboot command */
diff --git a/src/config/defaults/pcbios.h b/src/config/defaults/pcbios.h
index 21821c95..41afb903 100644
--- a/src/config/defaults/pcbios.h
+++ b/src/config/defaults/pcbios.h
@@ -48,6 +48,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define USB_HCD_EHCI /* EHCI USB host controller */
#define USB_HCD_UHCI /* UHCI USB host controller */
#define USB_KEYBOARD /* USB keyboards */
+#define USB_BLOCK /* USB block devices */
#define REBOOT_CMD /* Reboot command */
#define CPUID_CMD /* x86 CPU feature detection command */
diff --git a/src/config/usb.h b/src/config/usb.h
index d2519d87..4252ec22 100644
--- a/src/config/usb.h
+++ b/src/config/usb.h
@@ -25,6 +25,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*
*/
//#undef USB_KEYBOARD /* USB keyboards */
+//#undef USB_BLOCK /* USB block devices */
/*
* USB external interfaces
diff --git a/src/drivers/usb/usbblk.c b/src/drivers/usb/usbblk.c
new file mode 100644
index 00000000..a68e3ced
--- /dev/null
+++ b/src/drivers/usb/usbblk.c
@@ -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,
+};
diff --git a/src/drivers/usb/usbblk.h b/src/drivers/usb/usbblk.h
new file mode 100644
index 00000000..65d0705e
--- /dev/null
+++ b/src/drivers/usb/usbblk.h
@@ -0,0 +1,121 @@
+#ifndef _USBBLK_H
+#define _USBBLK_H
+
+/** @file
+ *
+ * USB mass storage driver
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+#include <ipxe/usb.h>
+#include <ipxe/scsi.h>
+#include <ipxe/interface.h>
+
+/** Mass storage class code */
+#define USB_CLASS_MSC 0x08
+
+/** SCSI command set subclass code */
+#define USB_SUBCLASS_MSC_SCSI 0x06
+
+/** Bulk-only transport protocol */
+#define USB_PROTOCOL_MSC_BULK 0x50
+
+/** Mass storage reset command */
+#define USBBLK_RESET ( USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE | \
+ USB_REQUEST_TYPE ( 255 ) )
+
+/** Command block wrapper */
+struct usbblk_command_wrapper {
+ /** Signature */
+ uint32_t signature;
+ /** Tag */
+ uint32_t tag;
+ /** Data transfer length */
+ uint32_t len;
+ /** Flags */
+ uint8_t flags;
+ /** LUN */
+ uint8_t lun;
+ /** Command block length */
+ uint8_t cblen;
+ /** Command block */
+ uint8_t cb[16];
+} __attribute__ (( packed ));
+
+/** Command block wrapper signature */
+#define USBBLK_COMMAND_SIGNATURE 0x43425355UL
+
+/** Command status wrapper */
+struct usbblk_status_wrapper {
+ /** Signature */
+ uint32_t signature;
+ /** Tag */
+ uint32_t tag;
+ /** Data residue */
+ uint32_t residue;
+ /** Status */
+ uint8_t status;
+} __attribute__ (( packed ));
+
+/** Command status wrapper signature */
+#define USBBLK_STATUS_SIGNATURE 0x53425355UL
+
+/** A USB mass storage command */
+struct usbblk_command {
+ /** SCSI command */
+ struct scsi_cmd scsi;
+ /** Command tag (0 for no command in progress) */
+ uint32_t tag;
+ /** Offset within data buffer */
+ size_t offset;
+};
+
+/** A USB mass storage device */
+struct usbblk_device {
+ /** Reference count */
+ struct refcnt refcnt;
+ /** List of devices */
+ struct list_head list;
+
+ /** USB function */
+ struct usb_function *func;
+ /** Bulk OUT endpoint */
+ struct usb_endpoint out;
+ /** Bulk IN endpoint */
+ struct usb_endpoint in;
+
+ /** SCSI command-issuing interface */
+ struct interface scsi;
+ /** SCSI data interface */
+ struct interface data;
+ /** Command process */
+ struct process process;
+ /** Device opened flag */
+ int opened;
+
+ /** Current command (if any) */
+ struct usbblk_command cmd;
+};
+
+/** Command tag magic
+ *
+ * This is a policy decision.
+ */
+#define USBBLK_TAG_MAGIC 0x18ae0000
+
+/** Maximum length of USB data block
+ *
+ * This is a policy decision.
+ */
+#define USBBLK_MAX_LEN 2048
+
+/** Maximum endpoint fill level
+ *
+ * This is a policy decision.
+ */
+#define USBBLK_MAX_FILL 4
+
+#endif /* _USBBLK_H */
diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h
index 242f91f8..8238d492 100644
--- a/src/include/ipxe/errfile.h
+++ b/src/include/ipxe/errfile.h
@@ -208,6 +208,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define ERRFILE_intelxl ( ERRFILE_DRIVER | 0x00cb0000 )
#define ERRFILE_pcimsix ( ERRFILE_DRIVER | 0x00cc0000 )
#define ERRFILE_intelxlvf ( ERRFILE_DRIVER | 0x00cd0000 )
+#define ERRFILE_usbblk ( ERRFILE_DRIVER | 0x00ce0000 )
#define ERRFILE_aoe ( ERRFILE_NET | 0x00000000 )
#define ERRFILE_arp ( ERRFILE_NET | 0x00010000 )