]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[usbblk] Add support for USB mass storage devices
authorMichael Brown <mcb30@ipxe.org>
Mon, 12 Oct 2020 14:31:49 +0000 (15:31 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 13 Oct 2020 14:56:38 +0000 (15:56 +0100)
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>
src/config/config_usb.c
src/config/defaults/efi.h
src/config/defaults/pcbios.h
src/config/usb.h
src/drivers/usb/usbblk.c [new file with mode: 0644]
src/drivers/usb/usbblk.h [new file with mode: 0644]
src/include/ipxe/errfile.h

index 17296d2776a596c07f43f54000dff9ba1e9a0c36..b679aeb2743ccb3272c27e03d9d915212d2d5d78 100644 (file)
@@ -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
index e707dfa6af2f13990d44f61ac97e198573d4c266..4ae6a316e9c9748f62d0f88443ea6879da7dee6e 100644 (file)
@@ -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 */
 
index 21821c95cb0527ed3029fa4e821580fdb104043d..41afb90335dbe5b9b9fa34f693c467a97f90227c 100644 (file)
@@ -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 */
index d2519d8777672233a8103f093a4daffb70c78c50..4252ec22935bb05dd19b80a68bfa98ea4927f447 100644 (file)
@@ -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 (file)
index 0000000..a68e3ce
--- /dev/null
@@ -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 (file)
index 0000000..65d0705
--- /dev/null
@@ -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 */
index 242f91f820987b3e3ace3f2762db05c71e621bde..8238d4925996b16a266cc520b0cbd48004826bac 100644 (file)
@@ -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 )