Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / ipxe / src / drivers / block / scsi.c
diff --git a/qemu/roms/ipxe/src/drivers/block/scsi.c b/qemu/roms/ipxe/src/drivers/block/scsi.c
new file mode 100644 (file)
index 0000000..64d6929
--- /dev/null
@@ -0,0 +1,999 @@
+/*
+ * Copyright (C) 2006 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 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <byteswap.h>
+#include <errno.h>
+#include <ipxe/list.h>
+#include <ipxe/process.h>
+#include <ipxe/xfer.h>
+#include <ipxe/blockdev.h>
+#include <ipxe/scsi.h>
+
+/** @file
+ *
+ * SCSI block device
+ *
+ */
+
+/** Maximum number of command retries */
+#define SCSICMD_MAX_RETRIES 10
+
+/* Error numbers generated by SCSI sense data */
+#define EIO_NO_SENSE __einfo_error ( EINFO_EIO_NO_SENSE )
+#define EINFO_EIO_NO_SENSE \
+       __einfo_uniqify ( EINFO_EIO, 0x00, "No sense" )
+#define EIO_RECOVERED_ERROR __einfo_error ( EINFO_EIO_RECOVERED_ERROR )
+#define EINFO_EIO_RECOVERED_ERROR \
+       __einfo_uniqify ( EINFO_EIO, 0x01, "Recovered error" )
+#define EIO_NOT_READY __einfo_error ( EINFO_EIO_NOT_READY )
+#define EINFO_EIO_NOT_READY \
+       __einfo_uniqify ( EINFO_EIO, 0x02, "Not ready" )
+#define EIO_MEDIUM_ERROR __einfo_error ( EINFO_EIO_MEDIUM_ERROR )
+#define EINFO_EIO_MEDIUM_ERROR \
+       __einfo_uniqify ( EINFO_EIO, 0x03, "Medium error" )
+#define EIO_HARDWARE_ERROR __einfo_error ( EINFO_EIO_HARDWARE_ERROR )
+#define EINFO_EIO_HARDWARE_ERROR \
+       __einfo_uniqify ( EINFO_EIO, 0x04, "Hardware error" )
+#define EIO_ILLEGAL_REQUEST __einfo_error ( EINFO_EIO_ILLEGAL_REQUEST )
+#define EINFO_EIO_ILLEGAL_REQUEST \
+       __einfo_uniqify ( EINFO_EIO, 0x05, "Illegal request" )
+#define EIO_UNIT_ATTENTION __einfo_error ( EINFO_EIO_UNIT_ATTENTION )
+#define EINFO_EIO_UNIT_ATTENTION \
+       __einfo_uniqify ( EINFO_EIO, 0x06, "Unit attention" )
+#define EIO_DATA_PROTECT __einfo_error ( EINFO_EIO_DATA_PROTECT )
+#define EINFO_EIO_DATA_PROTECT \
+       __einfo_uniqify ( EINFO_EIO, 0x07, "Data protect" )
+#define EIO_BLANK_CHECK __einfo_error ( EINFO_EIO_BLANK_CHECK )
+#define EINFO_EIO_BLANK_CHECK \
+       __einfo_uniqify ( EINFO_EIO, 0x08, "Blank check" )
+#define EIO_VENDOR_SPECIFIC __einfo_error ( EINFO_EIO_VENDOR_SPECIFIC )
+#define EINFO_EIO_VENDOR_SPECIFIC \
+       __einfo_uniqify ( EINFO_EIO, 0x09, "Vendor specific" )
+#define EIO_COPY_ABORTED __einfo_error ( EINFO_EIO_COPY_ABORTED )
+#define EINFO_EIO_COPY_ABORTED \
+       __einfo_uniqify ( EINFO_EIO, 0x0a, "Copy aborted" )
+#define EIO_ABORTED_COMMAND __einfo_error ( EINFO_EIO_ABORTED_COMMAND )
+#define EINFO_EIO_ABORTED_COMMAND \
+       __einfo_uniqify ( EINFO_EIO, 0x0b, "Aborted command" )
+#define EIO_RESERVED __einfo_error ( EINFO_EIO_RESERVED )
+#define EINFO_EIO_RESERVED \
+       __einfo_uniqify ( EINFO_EIO, 0x0c, "Reserved" )
+#define EIO_VOLUME_OVERFLOW __einfo_error ( EINFO_EIO_VOLUME_OVERFLOW )
+#define EINFO_EIO_VOLUME_OVERFLOW \
+       __einfo_uniqify ( EINFO_EIO, 0x0d, "Volume overflow" )
+#define EIO_MISCOMPARE __einfo_error ( EINFO_EIO_MISCOMPARE )
+#define EINFO_EIO_MISCOMPARE \
+       __einfo_uniqify ( EINFO_EIO, 0x0e, "Miscompare" )
+#define EIO_COMPLETED __einfo_error ( EINFO_EIO_COMPLETED )
+#define EINFO_EIO_COMPLETED \
+       __einfo_uniqify ( EINFO_EIO, 0x0f, "Completed" )
+#define EIO_SENSE( key )                                               \
+       EUNIQ ( EINFO_EIO, (key), EIO_NO_SENSE, EIO_RECOVERED_ERROR,    \
+               EIO_NOT_READY, EIO_MEDIUM_ERROR, EIO_HARDWARE_ERROR,    \
+               EIO_ILLEGAL_REQUEST, EIO_UNIT_ATTENTION,                \
+               EIO_DATA_PROTECT, EIO_BLANK_CHECK, EIO_VENDOR_SPECIFIC, \
+               EIO_COPY_ABORTED, EIO_ABORTED_COMMAND, EIO_RESERVED,    \
+               EIO_VOLUME_OVERFLOW, EIO_MISCOMPARE, EIO_COMPLETED )
+
+/******************************************************************************
+ *
+ * Utility functions
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Parse SCSI LUN
+ *
+ * @v lun_string       LUN string representation
+ * @v lun              LUN to fill in
+ * @ret rc             Return status code
+ */
+int scsi_parse_lun ( const char *lun_string, struct scsi_lun *lun ) {
+       char *p;
+       int i;
+
+       memset ( lun, 0, sizeof ( *lun ) );
+       if ( lun_string ) {
+               p = ( char * ) lun_string;
+               for ( i = 0 ; i < 4 ; i++ ) {
+                       lun->u16[i] = htons ( strtoul ( p, &p, 16 ) );
+                       if ( *p == '\0' )
+                               break;
+                       if ( *p != '-' )
+                               return -EINVAL;
+                       p++;
+               }
+               if ( *p )
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+/**
+ * Parse SCSI sense data
+ *
+ * @v data             Raw sense data
+ * @v len              Length of raw sense data
+ * @v sense            Descriptor-format sense data to fill in
+ */
+void scsi_parse_sense ( const void *data, size_t len,
+                       struct scsi_sns_descriptor *sense ) {
+       const union scsi_sns *sns = data;
+
+       /* Avoid returning uninitialised data */
+       memset ( sense, 0, sizeof ( *sense ) );
+
+       /* Copy, assuming descriptor-format data */
+       if ( len < sizeof ( sns->desc ) )
+               return;
+       memcpy ( sense, &sns->desc, sizeof ( *sense ) );
+
+       /* Convert fixed-format to descriptor-format, if applicable */
+       if ( len < sizeof ( sns->fixed ) )
+               return;
+       if ( ! SCSI_SENSE_FIXED ( sns->code ) )
+               return;
+       sense->additional = sns->fixed.additional;
+}
+
+/******************************************************************************
+ *
+ * Interface methods
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Issue SCSI command
+ *
+ * @v control          SCSI control interface
+ * @v data             SCSI data interface
+ * @v command          SCSI command
+ * @ret tag            Command tag, or negative error
+ */
+int scsi_command ( struct interface *control, struct interface *data,
+                  struct scsi_cmd *command ) {
+       struct interface *dest;
+       scsi_command_TYPE ( void * ) *op =
+               intf_get_dest_op ( control, scsi_command, &dest );
+       void *object = intf_object ( dest );
+       int tap;
+
+       if ( op ) {
+               tap = op ( object, data, command );
+       } else {
+               /* Default is to fail to issue the command */
+               tap = -EOPNOTSUPP;
+       }
+
+       intf_put ( dest );
+       return tap;
+}
+
+/**
+ * Report SCSI response
+ *
+ * @v interface                SCSI command interface
+ * @v response         SCSI response
+ */
+void scsi_response ( struct interface *intf, struct scsi_rsp *response ) {
+       struct interface *dest;
+       scsi_response_TYPE ( void * ) *op =
+               intf_get_dest_op ( intf, scsi_response, &dest );
+       void *object = intf_object ( dest );
+
+       if ( op ) {
+               op ( object, response );
+       } else {
+               /* Default is to ignore the response */
+       }
+
+       intf_put ( dest );
+}
+
+/******************************************************************************
+ *
+ * SCSI devices and commands
+ *
+ ******************************************************************************
+ */
+
+/** A SCSI device */
+struct scsi_device {
+       /** Reference count */
+       struct refcnt refcnt;
+       /** Block control interface */
+       struct interface block;
+       /** SCSI control interface */
+       struct interface scsi;
+
+       /** SCSI LUN */
+       struct scsi_lun lun;
+       /** Flags */
+       unsigned int flags;
+
+       /** TEST UNIT READY interface */
+       struct interface ready;
+       /** TEST UNIT READY process */
+       struct process process;
+
+       /** List of commands */
+       struct list_head cmds;
+};
+
+/** SCSI device flags */
+enum scsi_device_flags {
+       /** TEST UNIT READY has been issued */
+       SCSIDEV_UNIT_TESTED = 0x0001,
+       /** TEST UNIT READY has completed successfully */
+       SCSIDEV_UNIT_READY = 0x0002,
+};
+
+/** A SCSI command */
+struct scsi_command {
+       /** Reference count */
+       struct refcnt refcnt;
+       /** SCSI device */
+       struct scsi_device *scsidev;
+       /** List of SCSI commands */
+       struct list_head list;
+
+       /** Block data interface */
+       struct interface block;
+       /** SCSI data interface */
+       struct interface scsi;
+
+       /** Command type */
+       struct scsi_command_type *type;
+       /** Starting logical block address */
+       uint64_t lba;
+       /** Number of blocks */
+       unsigned int count;
+       /** Data buffer */
+       userptr_t buffer;
+       /** Length of data buffer */
+       size_t len;
+       /** Command tag */
+       uint32_t tag;
+
+       /** Retry count */
+       unsigned int retries;
+
+       /** Private data */
+       uint8_t priv[0];
+};
+
+/** A SCSI command type */
+struct scsi_command_type {
+       /** Name */
+       const char *name;
+       /** Additional working space */
+       size_t priv_len;
+       /**
+        * Construct SCSI command IU
+        *
+        * @v scsicmd           SCSI command
+        * @v command           SCSI command IU
+        */
+       void ( * cmd ) ( struct scsi_command *scsicmd,
+                        struct scsi_cmd *command );
+       /**
+        * Handle SCSI command completion
+        *
+        * @v scsicmd           SCSI command
+        * @v rc                Reason for completion
+        */
+       void ( * done ) ( struct scsi_command *scsicmd, int rc );
+};
+
+/**
+ * Get reference to SCSI device
+ *
+ * @v scsidev          SCSI device
+ * @ret scsidev                SCSI device
+ */
+static inline __attribute__ (( always_inline )) struct scsi_device *
+scsidev_get ( struct scsi_device *scsidev ) {
+       ref_get ( &scsidev->refcnt );
+       return scsidev;
+}
+
+/**
+ * Drop reference to SCSI device
+ *
+ * @v scsidev          SCSI device
+ */
+static inline __attribute__ (( always_inline )) void
+scsidev_put ( struct scsi_device *scsidev ) {
+       ref_put ( &scsidev->refcnt );
+}
+
+/**
+ * Get reference to SCSI command
+ *
+ * @v scsicmd          SCSI command
+ * @ret scsicmd                SCSI command
+ */
+static inline __attribute__ (( always_inline )) struct scsi_command *
+scsicmd_get ( struct scsi_command *scsicmd ) {
+       ref_get ( &scsicmd->refcnt );
+       return scsicmd;
+}
+
+/**
+ * Drop reference to SCSI command
+ *
+ * @v scsicmd          SCSI command
+ */
+static inline __attribute__ (( always_inline )) void
+scsicmd_put ( struct scsi_command *scsicmd ) {
+       ref_put ( &scsicmd->refcnt );
+}
+
+/**
+ * Get SCSI command private data
+ *
+ * @v scsicmd          SCSI command
+ * @ret priv           Private data
+ */
+static inline __attribute__ (( always_inline )) void *
+scsicmd_priv ( struct scsi_command *scsicmd ) {
+       return scsicmd->priv;
+}
+
+/**
+ * Free SCSI command
+ *
+ * @v refcnt           Reference count
+ */
+static void scsicmd_free ( struct refcnt *refcnt ) {
+       struct scsi_command *scsicmd =
+               container_of ( refcnt, struct scsi_command, refcnt );
+
+       /* Remove from list of commands */
+       list_del ( &scsicmd->list );
+       scsidev_put ( scsicmd->scsidev );
+
+       /* Free command */
+       free ( scsicmd );
+}
+
+/**
+ * Close SCSI command
+ *
+ * @v scsicmd          SCSI command
+ * @v rc               Reason for close
+ */
+static void scsicmd_close ( struct scsi_command *scsicmd, int rc ) {
+       struct scsi_device *scsidev = scsicmd->scsidev;
+
+       if ( rc != 0 ) {
+               DBGC ( scsidev, "SCSI %p tag %08x closed: %s\n",
+                      scsidev, scsicmd->tag, strerror ( rc ) );
+       }
+
+       /* Shut down interfaces */
+       intf_shutdown ( &scsicmd->scsi, rc );
+       intf_shutdown ( &scsicmd->block, rc );
+}
+
+/**
+ * Construct and issue SCSI command
+ *
+ * @ret rc             Return status code
+ */
+static int scsicmd_command ( struct scsi_command *scsicmd ) {
+       struct scsi_device *scsidev = scsicmd->scsidev;
+       struct scsi_cmd command;
+       int tag;
+       int rc;
+
+       /* Construct command */
+       memset ( &command, 0, sizeof ( command ) );
+       memcpy ( &command.lun, &scsidev->lun, sizeof ( command.lun ) );
+       scsicmd->type->cmd ( scsicmd, &command );
+
+       /* Issue command */
+       if ( ( tag = scsi_command ( &scsidev->scsi, &scsicmd->scsi,
+                                   &command ) ) < 0 ) {
+               rc = tag;
+               DBGC ( scsidev, "SCSI %p could not issue command: %s\n",
+                      scsidev, strerror ( rc ) );
+               return rc;
+       }
+
+       /* Record tag */
+       if ( scsicmd->tag ) {
+               DBGC ( scsidev, "SCSI %p tag %08x is now tag %08x\n",
+                      scsidev, scsicmd->tag, tag );
+       }
+       scsicmd->tag = tag;
+       DBGC2 ( scsidev, "SCSI %p tag %08x %s " SCSI_CDB_FORMAT "\n",
+               scsidev, scsicmd->tag, scsicmd->type->name,
+               SCSI_CDB_DATA ( command.cdb ) );
+
+       return 0;
+}
+
+/**
+ * Handle SCSI command completion
+ *
+ * @v scsicmd          SCSI command
+ * @v rc               Reason for close
+ */
+static void scsicmd_done ( struct scsi_command *scsicmd, int rc ) {
+       struct scsi_device *scsidev = scsicmd->scsidev;
+
+       /* Restart SCSI interface */
+       intf_restart ( &scsicmd->scsi, rc );
+
+       /* SCSI targets have an annoying habit of returning occasional
+        * pointless "error" messages such as "power-on occurred", so
+        * we have to be prepared to retry commands.
+        */
+       if ( ( rc != 0 ) && ( scsicmd->retries++ < SCSICMD_MAX_RETRIES ) ) {
+               /* Retry command */
+               DBGC ( scsidev, "SCSI %p tag %08x failed: %s\n",
+                      scsidev, scsicmd->tag, strerror ( rc ) );
+               DBGC ( scsidev, "SCSI %p tag %08x retrying (retry %d)\n",
+                      scsidev, scsicmd->tag, scsicmd->retries );
+               if ( ( rc = scsicmd_command ( scsicmd ) ) == 0 )
+                       return;
+       }
+
+       /* If we didn't (successfully) reissue the command, hand over
+        * to the command completion handler.
+        */
+       scsicmd->type->done ( scsicmd, rc );
+}
+
+/**
+ * Handle SCSI response
+ *
+ * @v scsicmd          SCSI command
+ * @v response         SCSI response
+ */
+static void scsicmd_response ( struct scsi_command *scsicmd,
+                              struct scsi_rsp *response ) {
+       struct scsi_device *scsidev = scsicmd->scsidev;
+       size_t overrun;
+       size_t underrun;
+       int rc;
+
+       if ( response->status == 0 ) {
+               scsicmd_done ( scsicmd, 0 );
+       } else {
+               DBGC ( scsidev, "SCSI %p tag %08x status %02x",
+                      scsidev, scsicmd->tag, response->status );
+               if ( response->overrun > 0 ) {
+                       overrun = response->overrun;
+                       DBGC ( scsidev, " overrun +%zd", overrun );
+               } else if ( response->overrun < 0 ) {
+                       underrun = -(response->overrun);
+                       DBGC ( scsidev, " underrun -%zd", underrun );
+               }
+               DBGC ( scsidev, " sense %02x key %02x additional %04x\n",
+                      ( response->sense.code & SCSI_SENSE_CODE_MASK ),
+                      ( response->sense.key & SCSI_SENSE_KEY_MASK ),
+                      ntohs ( response->sense.additional ) );
+
+               /* Construct error number from sense data */
+               rc = -EIO_SENSE ( response->sense.key & SCSI_SENSE_KEY_MASK );
+               scsicmd_done ( scsicmd, rc );
+       }
+}
+
+/**
+ * Construct SCSI READ command
+ *
+ * @v scsicmd          SCSI command
+ * @v command          SCSI command IU
+ */
+static void scsicmd_read_cmd ( struct scsi_command *scsicmd,
+                              struct scsi_cmd *command ) {
+
+       if ( ( scsicmd->lba + scsicmd->count ) > SCSI_MAX_BLOCK_10 ) {
+               /* Use READ (16) */
+               command->cdb.read16.opcode = SCSI_OPCODE_READ_16;
+               command->cdb.read16.lba = cpu_to_be64 ( scsicmd->lba );
+               command->cdb.read16.len = cpu_to_be32 ( scsicmd->count );
+       } else {
+               /* Use READ (10) */
+               command->cdb.read10.opcode = SCSI_OPCODE_READ_10;
+               command->cdb.read10.lba = cpu_to_be32 ( scsicmd->lba );
+               command->cdb.read10.len = cpu_to_be16 ( scsicmd->count );
+       }
+       command->data_in = scsicmd->buffer;
+       command->data_in_len = scsicmd->len;
+}
+
+/** SCSI READ command type */
+static struct scsi_command_type scsicmd_read = {
+       .name = "READ",
+       .cmd = scsicmd_read_cmd,
+       .done = scsicmd_close,
+};
+
+/**
+ * Construct SCSI WRITE command
+ *
+ * @v scsicmd          SCSI command
+ * @v command          SCSI command IU
+ */
+static void scsicmd_write_cmd ( struct scsi_command *scsicmd,
+                               struct scsi_cmd *command ) {
+
+       if ( ( scsicmd->lba + scsicmd->count ) > SCSI_MAX_BLOCK_10 ) {
+               /* Use WRITE (16) */
+               command->cdb.write16.opcode = SCSI_OPCODE_WRITE_16;
+               command->cdb.write16.lba = cpu_to_be64 ( scsicmd->lba );
+               command->cdb.write16.len = cpu_to_be32 ( scsicmd->count );
+       } else {
+               /* Use WRITE (10) */
+               command->cdb.write10.opcode = SCSI_OPCODE_WRITE_10;
+               command->cdb.write10.lba = cpu_to_be32 ( scsicmd->lba );
+               command->cdb.write10.len = cpu_to_be16 ( scsicmd->count );
+       }
+       command->data_out = scsicmd->buffer;
+       command->data_out_len = scsicmd->len;
+}
+
+/** SCSI WRITE command type */
+static struct scsi_command_type scsicmd_write = {
+       .name = "WRITE",
+       .cmd = scsicmd_write_cmd,
+       .done = scsicmd_close,
+};
+
+/** SCSI READ CAPACITY private data */
+struct scsi_read_capacity_private {
+       /** Use READ CAPACITY (16) */
+       int use16;
+       /** Data buffer for READ CAPACITY commands */
+       union {
+               /** Data buffer for READ CAPACITY (10) */
+               struct scsi_capacity_10 capacity10;
+               /** Data buffer for READ CAPACITY (16) */
+               struct scsi_capacity_16 capacity16;
+       } capacity;
+};
+
+/**
+ * Construct SCSI READ CAPACITY command
+ *
+ * @v scsicmd          SCSI command
+ * @v command          SCSI command IU
+ */
+static void scsicmd_read_capacity_cmd ( struct scsi_command *scsicmd,
+                                       struct scsi_cmd *command ) {
+       struct scsi_read_capacity_private *priv = scsicmd_priv ( scsicmd );
+       struct scsi_cdb_read_capacity_16 *readcap16 = &command->cdb.readcap16;
+       struct scsi_cdb_read_capacity_10 *readcap10 = &command->cdb.readcap10;
+       struct scsi_capacity_16 *capacity16 = &priv->capacity.capacity16;
+       struct scsi_capacity_10 *capacity10 = &priv->capacity.capacity10;
+
+       if ( priv->use16 ) {
+               /* Use READ CAPACITY (16) */
+               readcap16->opcode = SCSI_OPCODE_SERVICE_ACTION_IN;
+               readcap16->service_action =
+                       SCSI_SERVICE_ACTION_READ_CAPACITY_16;
+               readcap16->len = cpu_to_be32 ( sizeof ( *capacity16 ) );
+               command->data_in = virt_to_user ( capacity16 );
+               command->data_in_len = sizeof ( *capacity16 );
+       } else {
+               /* Use READ CAPACITY (10) */
+               readcap10->opcode = SCSI_OPCODE_READ_CAPACITY_10;
+               command->data_in = virt_to_user ( capacity10 );
+               command->data_in_len = sizeof ( *capacity10 );
+       }
+}
+
+/**
+ * Handle SCSI READ CAPACITY command completion
+ *
+ * @v scsicmd          SCSI command
+ * @v rc               Reason for completion
+ */
+static void scsicmd_read_capacity_done ( struct scsi_command *scsicmd,
+                                        int rc ) {
+       struct scsi_read_capacity_private *priv = scsicmd_priv ( scsicmd );
+       struct scsi_capacity_16 *capacity16 = &priv->capacity.capacity16;
+       struct scsi_capacity_10 *capacity10 = &priv->capacity.capacity10;
+       struct block_device_capacity capacity;
+
+       /* Close if command failed */
+       if ( rc != 0 ) {
+               scsicmd_close ( scsicmd, rc );
+               return;
+       }
+
+       /* Extract capacity */
+       if ( priv->use16 ) {
+               capacity.blocks = ( be64_to_cpu ( capacity16->lba ) + 1 );
+               capacity.blksize = be32_to_cpu ( capacity16->blksize );
+       } else {
+               capacity.blocks = ( be32_to_cpu ( capacity10->lba ) + 1 );
+               capacity.blksize = be32_to_cpu ( capacity10->blksize );
+
+               /* If capacity range was exceeded (i.e. capacity.lba
+                * was 0xffffffff, meaning that blockdev->blocks is
+                * now zero), use READ CAPACITY (16) instead.  READ
+                * CAPACITY (16) is not mandatory, so we can't just
+                * use it straight off.
+                */
+               if ( capacity.blocks == 0 ) {
+                       priv->use16 = 1;
+                       if ( ( rc = scsicmd_command ( scsicmd ) ) != 0 ) {
+                               scsicmd_close ( scsicmd, rc );
+                               return;
+                       }
+                       return;
+               }
+       }
+       capacity.max_count = -1U;
+
+       /* Return capacity to caller */
+       block_capacity ( &scsicmd->block, &capacity );
+
+       /* Close command */
+       scsicmd_close ( scsicmd, 0 );
+}
+
+/** SCSI READ CAPACITY command type */
+static struct scsi_command_type scsicmd_read_capacity = {
+       .name = "READ CAPACITY",
+       .priv_len = sizeof ( struct scsi_read_capacity_private ),
+       .cmd = scsicmd_read_capacity_cmd,
+       .done = scsicmd_read_capacity_done,
+};
+
+/**
+ * Construct SCSI TEST UNIT READY command
+ *
+ * @v scsicmd          SCSI command
+ * @v command          SCSI command IU
+ */
+static void scsicmd_test_unit_ready_cmd ( struct scsi_command *scsicmd __unused,
+                                         struct scsi_cmd *command ) {
+       struct scsi_cdb_test_unit_ready *testready = &command->cdb.testready;
+
+       testready->opcode = SCSI_OPCODE_TEST_UNIT_READY;
+}
+
+/** SCSI TEST UNIT READY command type */
+static struct scsi_command_type scsicmd_test_unit_ready = {
+       .name = "TEST UNIT READY",
+       .cmd = scsicmd_test_unit_ready_cmd,
+       .done = scsicmd_close,
+};
+
+/** SCSI command block interface operations */
+static struct interface_operation scsicmd_block_op[] = {
+       INTF_OP ( intf_close, struct scsi_command *, scsicmd_close ),
+};
+
+/** SCSI command block interface descriptor */
+static struct interface_descriptor scsicmd_block_desc =
+       INTF_DESC_PASSTHRU ( struct scsi_command, block,
+                            scsicmd_block_op, scsi );
+
+/** SCSI command SCSI interface operations */
+static struct interface_operation scsicmd_scsi_op[] = {
+       INTF_OP ( intf_close, struct scsi_command *, scsicmd_done ),
+       INTF_OP ( scsi_response, struct scsi_command *, scsicmd_response ),
+};
+
+/** SCSI command SCSI interface descriptor */
+static struct interface_descriptor scsicmd_scsi_desc =
+       INTF_DESC_PASSTHRU ( struct scsi_command, scsi,
+                            scsicmd_scsi_op, block );
+
+/**
+ * Create SCSI command
+ *
+ * @v scsidev          SCSI device
+ * @v block            Block data interface
+ * @v type             SCSI command type
+ * @v lba              Starting logical block address
+ * @v count            Number of blocks to transfer
+ * @v buffer           Data buffer
+ * @v len              Length of data buffer
+ * @ret rc             Return status code
+ */
+static int scsidev_command ( struct scsi_device *scsidev,
+                            struct interface *block,
+                            struct scsi_command_type *type,
+                            uint64_t lba, unsigned int count,
+                            userptr_t buffer, size_t len ) {
+       struct scsi_command *scsicmd;
+       int rc;
+
+       /* Allocate and initialise structure */
+       scsicmd = zalloc ( sizeof ( *scsicmd ) + type->priv_len );
+       if ( ! scsicmd ) {
+               rc = -ENOMEM;
+               goto err_zalloc;
+       }
+       ref_init ( &scsicmd->refcnt, scsicmd_free );
+       intf_init ( &scsicmd->block, &scsicmd_block_desc, &scsicmd->refcnt );
+       intf_init ( &scsicmd->scsi, &scsicmd_scsi_desc,
+                   &scsicmd->refcnt );
+       scsicmd->scsidev = scsidev_get ( scsidev );
+       list_add ( &scsicmd->list, &scsidev->cmds );
+       scsicmd->type = type;
+       scsicmd->lba = lba;
+       scsicmd->count = count;
+       scsicmd->buffer = buffer;
+       scsicmd->len = len;
+
+       /* Issue SCSI command */
+       if ( ( rc = scsicmd_command ( scsicmd ) ) != 0 )
+               goto err_command;
+
+       /* Attach to parent interface, mortalise self, and return */
+       intf_plug_plug ( &scsicmd->block, block );
+       ref_put ( &scsicmd->refcnt );
+       return 0;
+
+ err_command:
+       scsicmd_close ( scsicmd, rc );
+       ref_put ( &scsicmd->refcnt );
+ err_zalloc:
+       return rc;
+}
+
+/**
+ * Issue SCSI block read
+ *
+ * @v scsidev          SCSI device
+ * @v block            Block data interface
+ * @v lba              Starting logical block address
+ * @v count            Number of blocks to transfer
+ * @v buffer           Data buffer
+ * @v len              Length of data buffer
+ * @ret rc             Return status code
+
+ */
+static int scsidev_read ( struct scsi_device *scsidev,
+                         struct interface *block,
+                         uint64_t lba, unsigned int count,
+                         userptr_t buffer, size_t len ) {
+       return scsidev_command ( scsidev, block, &scsicmd_read,
+                                lba, count, buffer, len );
+}
+
+/**
+ * Issue SCSI block write
+ *
+ * @v scsidev          SCSI device
+ * @v block            Block data interface
+ * @v lba              Starting logical block address
+ * @v count            Number of blocks to transfer
+ * @v buffer           Data buffer
+ * @v len              Length of data buffer
+ * @ret rc             Return status code
+ */
+static int scsidev_write ( struct scsi_device *scsidev,
+                          struct interface *block,
+                          uint64_t lba, unsigned int count,
+                          userptr_t buffer, size_t len ) {
+       return scsidev_command ( scsidev, block, &scsicmd_write,
+                                lba, count, buffer, len );
+}
+
+/**
+ * Read SCSI device capacity
+ *
+ * @v scsidev          SCSI device
+ * @v block            Block data interface
+ * @ret rc             Return status code
+ */
+static int scsidev_read_capacity ( struct scsi_device *scsidev,
+                                  struct interface *block ) {
+       return scsidev_command ( scsidev, block, &scsicmd_read_capacity,
+                                0, 0, UNULL, 0 );
+}
+
+/**
+ * Test to see if SCSI device is ready
+ *
+ * @v scsidev          SCSI device
+ * @v block            Block data interface
+ * @ret rc             Return status code
+ */
+static int scsidev_test_unit_ready ( struct scsi_device *scsidev,
+                                    struct interface *block ) {
+       return scsidev_command ( scsidev, block, &scsicmd_test_unit_ready,
+                                0, 0, UNULL, 0 );
+}
+
+/**
+ * Check SCSI device flow-control window
+ *
+ * @v scsidev          SCSI device
+ * @ret len            Length of window
+ */
+static size_t scsidev_window ( struct scsi_device *scsidev ) {
+
+       /* Refuse commands until unit is confirmed ready */
+       if ( ! ( scsidev->flags & SCSIDEV_UNIT_READY ) )
+               return 0;
+
+       return xfer_window ( &scsidev->scsi );
+}
+
+/**
+ * Close SCSI device
+ *
+ * @v scsidev          SCSI device
+ * @v rc               Reason for close
+ */
+static void scsidev_close ( struct scsi_device *scsidev, int rc ) {
+       struct scsi_command *scsicmd;
+       struct scsi_command *tmp;
+
+       /* Stop process */
+       process_del ( &scsidev->process );
+
+       /* Shut down interfaces */
+       intf_shutdown ( &scsidev->block, rc );
+       intf_shutdown ( &scsidev->scsi, rc );
+       intf_shutdown ( &scsidev->ready, rc );
+
+       /* Shut down any remaining commands */
+       list_for_each_entry_safe ( scsicmd, tmp, &scsidev->cmds, list ) {
+               scsicmd_get ( scsicmd );
+               scsicmd_close ( scsicmd, rc );
+               scsicmd_put ( scsicmd );
+       }
+}
+
+/** SCSI device block interface operations */
+static struct interface_operation scsidev_block_op[] = {
+       INTF_OP ( xfer_window, struct scsi_device *, scsidev_window ),
+       INTF_OP ( block_read, struct scsi_device *, scsidev_read ),
+       INTF_OP ( block_write, struct scsi_device *, scsidev_write ),
+       INTF_OP ( block_read_capacity, struct scsi_device *,
+                 scsidev_read_capacity ),
+       INTF_OP ( intf_close, struct scsi_device *, scsidev_close ),
+};
+
+/** SCSI device block interface descriptor */
+static struct interface_descriptor scsidev_block_desc =
+       INTF_DESC_PASSTHRU ( struct scsi_device, block,
+                            scsidev_block_op, scsi );
+
+/**
+ * Handle SCSI TEST UNIT READY response
+ *
+ * @v scsidev          SCSI device
+ * @v rc               Reason for close
+ */
+static void scsidev_ready ( struct scsi_device *scsidev, int rc ) {
+
+       /* Shut down interface */
+       intf_shutdown ( &scsidev->ready, rc );
+
+       /* Close device on failure */
+       if ( rc != 0 ) {
+               DBGC ( scsidev, "SCSI %p not ready: %s\n",
+                      scsidev, strerror ( rc ) );
+               scsidev_close ( scsidev, rc );
+               return;
+       }
+
+       /* Mark device as ready */
+       scsidev->flags |= SCSIDEV_UNIT_READY;
+       xfer_window_changed ( &scsidev->block );
+       DBGC ( scsidev, "SCSI %p unit is ready\n", scsidev );
+}
+
+/** SCSI device TEST UNIT READY interface operations */
+static struct interface_operation scsidev_ready_op[] = {
+       INTF_OP ( intf_close, struct scsi_device *, scsidev_ready ),
+};
+
+/** SCSI device TEST UNIT READY interface descriptor */
+static struct interface_descriptor scsidev_ready_desc =
+       INTF_DESC ( struct scsi_device, ready, scsidev_ready_op );
+
+/**
+ * SCSI TEST UNIT READY process
+ *
+ * @v scsidev          SCSI device
+ */
+static void scsidev_step ( struct scsi_device *scsidev ) {
+       int rc;
+
+       /* Do nothing if we have already issued TEST UNIT READY */
+       if ( scsidev->flags & SCSIDEV_UNIT_TESTED )
+               return;
+
+       /* Wait until underlying SCSI device is ready */
+       if ( xfer_window ( &scsidev->scsi ) == 0 )
+               return;
+
+       DBGC ( scsidev, "SCSI %p waiting for unit to become ready\n",
+              scsidev );
+
+       /* Mark TEST UNIT READY as sent */
+       scsidev->flags |= SCSIDEV_UNIT_TESTED;
+
+       /* Issue TEST UNIT READY command */
+       if ( ( rc = scsidev_test_unit_ready ( scsidev, &scsidev->ready )) !=0){
+               scsidev_close ( scsidev, rc );
+               return;
+       }
+}
+
+/** SCSI device SCSI interface operations */
+static struct interface_operation scsidev_scsi_op[] = {
+       INTF_OP ( xfer_window_changed, struct scsi_device *, scsidev_step ),
+       INTF_OP ( intf_close, struct scsi_device *, scsidev_close ),
+};
+
+/** SCSI device SCSI interface descriptor */
+static struct interface_descriptor scsidev_scsi_desc =
+       INTF_DESC_PASSTHRU ( struct scsi_device, scsi,
+                            scsidev_scsi_op, block );
+
+/** SCSI device process descriptor */
+static struct process_descriptor scsidev_process_desc =
+       PROC_DESC_ONCE ( struct scsi_device, process, scsidev_step );
+
+/**
+ * Open SCSI device
+ *
+ * @v block            Block control interface
+ * @v scsi             SCSI control interface
+ * @v lun              SCSI LUN
+ * @ret rc             Return status code
+ */
+int scsi_open ( struct interface *block, struct interface *scsi,
+               struct scsi_lun *lun ) {
+       struct scsi_device *scsidev;
+
+       /* Allocate and initialise structure */
+       scsidev = zalloc ( sizeof ( *scsidev ) );
+       if ( ! scsidev )
+               return -ENOMEM;
+       ref_init ( &scsidev->refcnt, NULL );
+       intf_init ( &scsidev->block, &scsidev_block_desc, &scsidev->refcnt );
+       intf_init ( &scsidev->scsi, &scsidev_scsi_desc, &scsidev->refcnt );
+       intf_init ( &scsidev->ready, &scsidev_ready_desc, &scsidev->refcnt );
+       process_init ( &scsidev->process, &scsidev_process_desc,
+                      &scsidev->refcnt );
+       INIT_LIST_HEAD ( &scsidev->cmds );
+       memcpy ( &scsidev->lun, lun, sizeof ( scsidev->lun ) );
+       DBGC ( scsidev, "SCSI %p created for LUN " SCSI_LUN_FORMAT "\n",
+              scsidev, SCSI_LUN_DATA ( scsidev->lun ) );
+
+       /* Attach to SCSI and parent interfaces, mortalise self, and return */
+       intf_plug_plug ( &scsidev->scsi, scsi );
+       intf_plug_plug ( &scsidev->block, block );
+       ref_put ( &scsidev->refcnt );
+       return 0;
+}