Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / ipxe / src / drivers / block / srp.c
diff --git a/qemu/roms/ipxe/src/drivers/block/srp.c b/qemu/roms/ipxe/src/drivers/block/srp.c
new file mode 100644 (file)
index 0000000..7edf69a
--- /dev/null
@@ -0,0 +1,829 @@
+/*
+ * Copyright (C) 2009 Fen Systems Ltd <mbrown@fensystems.co.uk>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ *   Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+FILE_LICENCE ( BSD2 );
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ipxe/scsi.h>
+#include <ipxe/xfer.h>
+#include <ipxe/features.h>
+#include <ipxe/srp.h>
+
+/**
+ * @file
+ *
+ * SCSI RDMA Protocol
+ *
+ */
+
+FEATURE ( FEATURE_PROTOCOL, "SRP", DHCP_EB_FEATURE_SRP, 1 );
+
+/** Maximum length of any initiator-to-target IU that we will send
+ *
+ * The longest IU is a SRP_CMD with no additional CDB and two direct
+ * data buffer descriptors, which comes to 80 bytes.
+ */
+#define SRP_MAX_I_T_IU_LEN 80
+
+/* Error numbers generated by SRP login rejection */
+#define EINFO_SRP_LOGIN_REJ( reason, desc )                                  \
+       __einfo_uniqify ( EINFO_EPERM, ( (reason) & 0x0f ), desc )
+#define EPERM_UNKNOWN                                                        \
+       __einfo_error ( EINFO_EPERM_UNKNOWN )
+#define EINFO_EPERM_UNKNOWN EINFO_SRP_LOGIN_REJ (                            \
+       SRP_LOGIN_REJ_REASON_UNKNOWN,                                         \
+       "Unable to establish RDMA channel, no reason specified" )
+#define EPERM_INSUFFICIENT_RESOURCES                                         \
+       __einfo_error ( EINFO_EPERM_INSUFFICIENT_RESOURCES )
+#define EINFO_EPERM_INSUFFICIENT_RESOURCES EINFO_SRP_LOGIN_REJ (             \
+       SRP_LOGIN_REJ_REASON_INSUFFICIENT_RESOURCES,                          \
+       "Insufficient RDMA channel resources" )
+#define EPERM_BAD_MAX_I_T_IU_LEN                                             \
+       __einfo_error ( EINFO_EPERM_BAD_MAX_I_T_IU_LEN )
+#define EINFO_EPERM_BAD_MAX_I_T_IU_LEN EINFO_SRP_LOGIN_REJ (                 \
+       SRP_LOGIN_REJ_REASON_BAD_MAX_I_T_IU_LEN,                              \
+       "Requested maximum initiator to target IU length value too large" )
+#define EPERM_CANNOT_ASSOCIATE                                               \
+       __einfo_error ( EINFO_EPERM_CANNOT_ASSOCIATE )
+#define EINFO_EPERM_CANNOT_ASSOCIATE EINFO_SRP_LOGIN_REJ (                   \
+       SRP_LOGIN_REJ_REASON_CANNOT_ASSOCIATE,                                \
+       "Unable to associate RDMA channel with specified I_T nexus" )
+#define EPERM_UNSUPPORTED_BUFFER_FORMAT                                              \
+       __einfo_error ( EINFO_EPERM_UNSUPPORTED_BUFFER_FORMAT )
+#define EINFO_EPERM_UNSUPPORTED_BUFFER_FORMAT EINFO_SRP_LOGIN_REJ (          \
+       SRP_LOGIN_REJ_REASON_UNSUPPORTED_BUFFER_FORMAT,                       \
+       "One or more requested data buffer descriptor formats not supported" )
+#define EPERM_NO_MULTIPLE_CHANNELS                                           \
+       __einfo_error ( EINFO_EPERM_NO_MULTIPLE_CHANNELS )
+#define EINFO_EPERM_NO_MULTIPLE_CHANNELS EINFO_SRP_LOGIN_REJ (               \
+       SRP_LOGIN_REJ_REASON_NO_MULTIPLE_CHANNELS,                            \
+       "SRP target does not support multiple RDMA channels per I_T nexus" )
+#define EPERM_NO_MORE_CHANNELS                                               \
+       __einfo_error ( EINFO_EPERM_NO_MORE_CHANNELS )
+#define EINFO_EPERM_NO_MORE_CHANNELS EINFO_SRP_LOGIN_REJ (                   \
+       SRP_LOGIN_REJ_REASON_NO_MORE_CHANNELS,                                \
+       "RDMA channel limit reached for this initiator" )
+#define EPERM_LOGIN_REJ( reason_nibble )                                     \
+       EUNIQ ( EINFO_EPERM, (reason_nibble), EPERM_UNKNOWN,                  \
+               EPERM_INSUFFICIENT_RESOURCES, EPERM_BAD_MAX_I_T_IU_LEN,       \
+               EPERM_CANNOT_ASSOCIATE, EPERM_UNSUPPORTED_BUFFER_FORMAT,      \
+               EPERM_NO_MULTIPLE_CHANNELS, EPERM_NO_MORE_CHANNELS )
+
+/** An SRP device */
+struct srp_device {
+       /** Reference count */
+       struct refcnt refcnt;
+
+       /** SCSI command issuing interface */
+       struct interface scsi;
+       /** Underlying data transfer interface */
+       struct interface socket;
+
+       /** RDMA memory handle */
+       uint32_t memory_handle;
+       /** Login completed successfully */
+       int logged_in;
+
+       /** Initiator port ID (for boot firmware table) */
+       union srp_port_id initiator;
+       /** Target port ID (for boot firmware table) */
+       union srp_port_id target;
+       /** SCSI LUN (for boot firmware table) */
+       struct scsi_lun lun;
+
+       /** List of active commands */
+       struct list_head commands;
+};
+
+/** An SRP command */
+struct srp_command {
+       /** Reference count */
+       struct refcnt refcnt;
+       /** SRP device */
+       struct srp_device *srpdev;
+       /** List of active commands */
+       struct list_head list;
+
+       /** SCSI command interface */
+       struct interface scsi;
+       /** Command tag */
+       uint32_t tag;
+};
+
+/**
+ * Get reference to SRP device
+ *
+ * @v srpdev           SRP device
+ * @ret srpdev         SRP device
+ */
+static inline __attribute__ (( always_inline )) struct srp_device *
+srpdev_get ( struct srp_device *srpdev ) {
+       ref_get ( &srpdev->refcnt );
+       return srpdev;
+}
+
+/**
+ * Drop reference to SRP device
+ *
+ * @v srpdev           SRP device
+ */
+static inline __attribute__ (( always_inline )) void
+srpdev_put ( struct srp_device *srpdev ) {
+       ref_put ( &srpdev->refcnt );
+}
+
+/**
+ * Get reference to SRP command
+ *
+ * @v srpcmd           SRP command
+ * @ret srpcmd         SRP command
+ */
+static inline __attribute__ (( always_inline )) struct srp_command *
+srpcmd_get ( struct srp_command *srpcmd ) {
+       ref_get ( &srpcmd->refcnt );
+       return srpcmd;
+}
+
+/**
+ * Drop reference to SRP command
+ *
+ * @v srpcmd           SRP command
+ */
+static inline __attribute__ (( always_inline )) void
+srpcmd_put ( struct srp_command *srpcmd ) {
+       ref_put ( &srpcmd->refcnt );
+}
+
+/**
+ * Free SRP command
+ *
+ * @v refcnt           Reference count
+ */
+static void srpcmd_free ( struct refcnt *refcnt ) {
+       struct srp_command *srpcmd =
+               container_of ( refcnt, struct srp_command, refcnt );
+
+       assert ( list_empty ( &srpcmd->list ) );
+
+       srpdev_put ( srpcmd->srpdev );
+       free ( srpcmd );
+}
+
+/**
+ * Close SRP command
+ *
+ * @v srpcmd           SRP command
+ * @v rc               Reason for close
+ */
+static void srpcmd_close ( struct srp_command *srpcmd, int rc ) {
+       struct srp_device *srpdev = srpcmd->srpdev;
+
+       if ( rc != 0 ) {
+               DBGC ( srpdev, "SRP %p tag %08x closed: %s\n",
+                      srpdev, srpcmd->tag, strerror ( rc ) );
+       }
+
+       /* Remove from list of commands */
+       if ( ! list_empty ( &srpcmd->list ) ) {
+               list_del ( &srpcmd->list );
+               INIT_LIST_HEAD ( &srpcmd->list );
+               srpcmd_put ( srpcmd );
+       }
+
+       /* Shut down interfaces */
+       intf_shutdown ( &srpcmd->scsi, rc );
+}
+
+/**
+ * Close SRP device
+ *
+ * @v srpdev           SRP device
+ * @v rc               Reason for close
+ */
+static void srpdev_close ( struct srp_device *srpdev, int rc ) {
+       struct srp_command *srpcmd;
+       struct srp_command *tmp;
+
+       if ( rc != 0 ) {
+               DBGC ( srpdev, "SRP %p closed: %s\n",
+                      srpdev, strerror ( rc ) );
+       }
+
+       /* Shut down interfaces */
+       intf_shutdown ( &srpdev->socket, rc );
+       intf_shutdown ( &srpdev->scsi, rc );
+
+       /* Shut down any active commands */
+       list_for_each_entry_safe ( srpcmd, tmp, &srpdev->commands, list ) {
+               srpcmd_get ( srpcmd );
+               srpcmd_close ( srpcmd, rc );
+               srpcmd_put ( srpcmd );
+       }
+}
+
+/**
+ * Identify SRP command by tag
+ *
+ * @v srpdev           SRP device
+ * @v tag              Command tag
+ * @ret srpcmd         SRP command, or NULL
+ */
+static struct srp_command * srp_find_tag ( struct srp_device *srpdev,
+                                          uint32_t tag ) {
+       struct srp_command *srpcmd;
+
+       list_for_each_entry ( srpcmd, &srpdev->commands, list ) {
+               if ( srpcmd->tag == tag )
+                       return srpcmd;
+       }
+       return NULL;
+}
+
+/**
+ * Choose an SRP command tag
+ *
+ * @v srpdev           SRP device
+ * @ret tag            New tag, or negative error
+ */
+static int srp_new_tag ( struct srp_device *srpdev ) {
+       static uint16_t tag_idx;
+       unsigned int i;
+
+       for ( i = 0 ; i < 65536 ; i++ ) {
+               tag_idx++;
+               if ( srp_find_tag ( srpdev, tag_idx ) == NULL )
+                       return tag_idx;
+       }
+       return -EADDRINUSE;
+}
+
+/**
+ * Transmit SRP login request
+ *
+ * @v srpdev           SRP device
+ * @v initiator                Initiator port ID
+ * @v target           Target port ID
+ * @v tag              Command tag
+ * @ret rc             Return status code
+ */
+static int srp_login ( struct srp_device *srpdev, union srp_port_id *initiator,
+                      union srp_port_id *target, uint32_t tag ) {
+       struct io_buffer *iobuf;
+       struct srp_login_req *login_req;
+       int rc;
+
+       /* Allocate I/O buffer */
+       iobuf = xfer_alloc_iob ( &srpdev->socket, sizeof ( *login_req ) );
+       if ( ! iobuf )
+               return -ENOMEM;
+
+       /* Construct login request IU */
+       login_req = iob_put ( iobuf, sizeof ( *login_req ) );
+       memset ( login_req, 0, sizeof ( *login_req ) );
+       login_req->type = SRP_LOGIN_REQ;
+       login_req->tag.dwords[0] = htonl ( SRP_TAG_MAGIC );
+       login_req->tag.dwords[1] = htonl ( tag );
+       login_req->max_i_t_iu_len = htonl ( SRP_MAX_I_T_IU_LEN );
+       login_req->required_buffer_formats = SRP_LOGIN_REQ_FMT_DDBD;
+       memcpy ( &login_req->initiator, initiator,
+                sizeof ( login_req->initiator ) );
+       memcpy ( &login_req->target, target, sizeof ( login_req->target ) );
+
+       DBGC ( srpdev, "SRP %p tag %08x LOGIN_REQ:\n", srpdev, tag );
+       DBGC_HDA ( srpdev, 0, iobuf->data, iob_len ( iobuf ) );
+
+       /* Send login request IU */
+       if ( ( rc = xfer_deliver_iob ( &srpdev->socket, iobuf ) ) != 0 ) {
+               DBGC ( srpdev, "SRP %p tag %08x could not send LOGIN_REQ: "
+                      "%s\n", srpdev, tag, strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Receive SRP login response
+ *
+ * @v srpdev           SRP device
+ * @v data             SRP IU
+ * @v len              Length of SRP IU
+ * @ret rc             Return status code
+ */
+static int srp_login_rsp ( struct srp_device *srpdev,
+                          const void *data, size_t len ) {
+       const struct srp_login_rsp *login_rsp = data;
+
+       /* Sanity check */
+       if ( len < sizeof ( *login_rsp ) ) {
+               DBGC ( srpdev, "SRP %p LOGIN_RSP too short (%zd bytes)\n",
+                      srpdev, len );
+               return -EINVAL;
+       }
+       DBGC ( srpdev, "SRP %p tag %08x LOGIN_RSP:\n",
+              srpdev, ntohl ( login_rsp->tag.dwords[1] ) );
+       DBGC_HDA ( srpdev, 0, data, len );
+
+       /* Mark as logged in */
+       srpdev->logged_in = 1;
+       DBGC ( srpdev, "SRP %p logged in\n", srpdev );
+
+       /* Notify of window change */
+       xfer_window_changed ( &srpdev->scsi );
+
+       return 0;
+}
+
+/**
+ * Receive SRP login rejection
+ *
+ * @v srpdev           SRP device
+ * @v data             SRP IU
+ * @v len              Length of SRP IU
+ * @ret rc             Return status code
+ */
+static int srp_login_rej ( struct srp_device *srpdev,
+                          const void *data, size_t len ) {
+       const struct srp_login_rej *login_rej = data;
+       uint32_t reason;
+
+       /* Sanity check */
+       if ( len < sizeof ( *login_rej ) ) {
+               DBGC ( srpdev, "SRP %p LOGIN_REJ too short (%zd bytes)\n",
+                      srpdev, len );
+               return -EINVAL;
+       }
+       reason = ntohl ( login_rej->reason );
+       DBGC ( srpdev, "SRP %p tag %08x LOGIN_REJ reason %08x:\n",
+              srpdev, ntohl ( login_rej->tag.dwords[1] ), reason );
+       DBGC_HDA ( srpdev, 0, data, len );
+
+       /* Login rejection always indicates an error */
+       return ( SRP_LOGIN_REJ_REASON_DEFINED ( reason ) ?
+                -EPERM_LOGIN_REJ ( reason ) : -EACCES );
+}
+
+/**
+ * Transmit SRP SCSI command
+ *
+ * @v srpdev           SRP device
+ * @v command          SCSI command
+ * @v tag              Command tag
+ * @ret rc             Return status code
+ */
+static int srp_cmd ( struct srp_device *srpdev,
+                    struct scsi_cmd *command,
+                    uint32_t tag ) {
+       struct io_buffer *iobuf;
+       struct srp_cmd *cmd;
+       struct srp_memory_descriptor *data_out;
+       struct srp_memory_descriptor *data_in;
+       int rc;
+
+       /* Sanity check */
+       if ( ! srpdev->logged_in ) {
+               DBGC ( srpdev, "SRP %p tag %08x cannot send CMD before "
+                      "login completes\n", srpdev, tag );
+               return -EBUSY;
+       }
+
+       /* Allocate I/O buffer */
+       iobuf = xfer_alloc_iob ( &srpdev->socket, SRP_MAX_I_T_IU_LEN );
+       if ( ! iobuf )
+               return -ENOMEM;
+
+       /* Construct base portion */
+       cmd = iob_put ( iobuf, sizeof ( *cmd ) );
+       memset ( cmd, 0, sizeof ( *cmd ) );
+       cmd->type = SRP_CMD;
+       cmd->tag.dwords[0] = htonl ( SRP_TAG_MAGIC );
+       cmd->tag.dwords[1] = htonl ( tag );
+       memcpy ( &cmd->lun, &command->lun, sizeof ( cmd->lun ) );
+       memcpy ( &cmd->cdb, &command->cdb, sizeof ( cmd->cdb ) );
+
+       /* Construct data-out descriptor, if present */
+       if ( command->data_out ) {
+               cmd->data_buffer_formats |= SRP_CMD_DO_FMT_DIRECT;
+               data_out = iob_put ( iobuf, sizeof ( *data_out ) );
+               data_out->address =
+                   cpu_to_be64 ( user_to_phys ( command->data_out, 0 ) );
+               data_out->handle = ntohl ( srpdev->memory_handle );
+               data_out->len = ntohl ( command->data_out_len );
+       }
+
+       /* Construct data-in descriptor, if present */
+       if ( command->data_in ) {
+               cmd->data_buffer_formats |= SRP_CMD_DI_FMT_DIRECT;
+               data_in = iob_put ( iobuf, sizeof ( *data_in ) );
+               data_in->address =
+                    cpu_to_be64 ( user_to_phys ( command->data_in, 0 ) );
+               data_in->handle = ntohl ( srpdev->memory_handle );
+               data_in->len = ntohl ( command->data_in_len );
+       }
+
+       DBGC2 ( srpdev, "SRP %p tag %08x CMD " SCSI_CDB_FORMAT "\n",
+               srpdev, tag, SCSI_CDB_DATA ( cmd->cdb ) );
+
+       /* Send IU */
+       if ( ( rc = xfer_deliver_iob ( &srpdev->socket, iobuf ) ) != 0 ) {
+               DBGC ( srpdev, "SRP %p tag %08x could not send CMD: %s\n",
+                      srpdev, tag, strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Receive SRP SCSI response
+ *
+ * @v srpdev           SRP device
+ * @v data             SRP IU
+ * @v len              Length of SRP IU
+ * @ret rc             Returns status code
+ */
+static int srp_rsp ( struct srp_device *srpdev,
+                    const void *data, size_t len ) {
+       const struct srp_rsp *rsp = data;
+       struct srp_command *srpcmd;
+       struct scsi_rsp response;
+       ssize_t data_out_residual_count;
+       ssize_t data_in_residual_count;
+
+       /* Sanity check */
+       if ( ( len < sizeof ( *rsp ) ) ||
+            ( len < ( sizeof ( *rsp ) +
+                      srp_rsp_response_data_len ( rsp ) +
+                      srp_rsp_sense_data_len ( rsp ) ) ) ) {
+               DBGC ( srpdev, "SRP %p RSP too short (%zd bytes)\n",
+                      srpdev, len );
+               return -EINVAL;
+       }
+       DBGC2 ( srpdev, "SRP %p tag %08x RSP stat %02x dores %08x dires "
+               "%08x valid %02x%s%s%s%s%s%s\n",
+               srpdev, ntohl ( rsp->tag.dwords[1] ), rsp->status,
+               ntohl ( rsp->data_out_residual_count ),
+               ntohl ( rsp->data_in_residual_count ), rsp->valid,
+               ( ( rsp->valid & SRP_RSP_VALID_DIUNDER ) ? " diunder" : "" ),
+               ( ( rsp->valid & SRP_RSP_VALID_DIOVER ) ? " diover" : "" ),
+               ( ( rsp->valid & SRP_RSP_VALID_DOUNDER ) ? " dounder" : "" ),
+               ( ( rsp->valid & SRP_RSP_VALID_DOOVER ) ? " doover" : "" ),
+               ( ( rsp->valid & SRP_RSP_VALID_SNSVALID ) ? " sns" : "" ),
+               ( ( rsp->valid & SRP_RSP_VALID_RSPVALID ) ? " rsp" : "" ) );
+
+       /* Identify command by tag */
+       srpcmd = srp_find_tag ( srpdev, ntohl ( rsp->tag.dwords[1] ) );
+       if ( ! srpcmd ) {
+               DBGC ( srpdev, "SRP %p tag %08x unrecognised RSP\n",
+                      srpdev, ntohl ( rsp->tag.dwords[1] ) );
+               return -ENOENT;
+       }
+
+       /* Hold command reference for remainder of function */
+       srpcmd_get ( srpcmd );
+
+       /* Build SCSI response */
+       memset ( &response, 0, sizeof ( response ) );
+       response.status = rsp->status;
+       data_out_residual_count = ntohl ( rsp->data_out_residual_count );
+       data_in_residual_count = ntohl ( rsp->data_in_residual_count );
+       if ( rsp->valid & SRP_RSP_VALID_DOOVER ) {
+               response.overrun = data_out_residual_count;
+       } else if ( rsp->valid & SRP_RSP_VALID_DOUNDER ) {
+               response.overrun = -(data_out_residual_count);
+       } else if ( rsp->valid & SRP_RSP_VALID_DIOVER ) {
+               response.overrun = data_in_residual_count;
+       } else if ( rsp->valid & SRP_RSP_VALID_DIUNDER ) {
+               response.overrun = -(data_in_residual_count);
+       }
+       scsi_parse_sense ( srp_rsp_sense_data ( rsp ),
+                          srp_rsp_sense_data_len ( rsp ), &response.sense );
+
+       /* Report SCSI response */
+       scsi_response ( &srpcmd->scsi, &response );
+
+       /* Close SCSI command */
+       srpcmd_close ( srpcmd, 0 );
+
+       /* Drop temporary command reference */
+       srpcmd_put ( srpcmd );
+
+       return 0;
+}
+
+/**
+ * Receive SRP unrecognised response IU
+ *
+ * @v srpdev           SRP device
+ * @v data             SRP IU
+ * @v len              Length of SRP IU
+ * @ret rc             Returns status code
+ */
+static int srp_unrecognised ( struct srp_device *srpdev,
+                             const void *data, size_t len ) {
+       const struct srp_common *common = data;
+
+       DBGC ( srpdev, "SRP %p tag %08x unrecognised IU type %02x:\n",
+              srpdev, ntohl ( common->tag.dwords[1] ), common->type );
+       DBGC_HDA ( srpdev, 0, data, len );
+
+       return -ENOTSUP;
+}
+
+/** SRP command SCSI interface operations */
+static struct interface_operation srpcmd_scsi_op[] = {
+       INTF_OP ( intf_close, struct srp_command *, srpcmd_close ),
+};
+
+/** SRP command SCSI interface descriptor */
+static struct interface_descriptor srpcmd_scsi_desc =
+       INTF_DESC ( struct srp_command, scsi, srpcmd_scsi_op );
+
+/**
+ * Issue SRP SCSI command
+ *
+ * @v srpdev           SRP device
+ * @v parent           Parent interface
+ * @v command          SCSI command
+ * @ret tag            Command tag, or negative error
+ */
+static int srpdev_scsi_command ( struct srp_device *srpdev,
+                                struct interface *parent,
+                                struct scsi_cmd *command ) {
+       struct srp_command *srpcmd;
+       int tag;
+       int rc;
+
+       /* Allocate command tag */
+       tag = srp_new_tag ( srpdev );
+       if ( tag < 0 ) {
+               rc = tag;
+               goto err_tag;
+       }
+
+       /* Allocate and initialise structure */
+       srpcmd = zalloc ( sizeof ( *srpcmd ) );
+       if ( ! srpcmd ) {
+               rc = -ENOMEM;
+               goto err_zalloc;
+       }
+       ref_init ( &srpcmd->refcnt, srpcmd_free );
+       intf_init ( &srpcmd->scsi, &srpcmd_scsi_desc, &srpcmd->refcnt );
+       srpcmd->srpdev = srpdev_get ( srpdev );
+       list_add ( &srpcmd->list, &srpdev->commands );
+       srpcmd->tag = tag;
+
+       /* Send command IU */
+       if ( ( rc = srp_cmd ( srpdev, command, srpcmd->tag ) ) != 0 )
+               goto err_cmd;
+
+       /* Attach to parent interface, leave reference with command
+        * list, and return.
+        */
+       intf_plug_plug ( &srpcmd->scsi, parent );
+       return srpcmd->tag;
+
+ err_cmd:
+       srpcmd_close ( srpcmd, rc );
+ err_zalloc:
+ err_tag:
+       return rc;
+}
+
+/**
+ * Receive data from SRP socket
+ *
+ * @v srpdev           SRP device
+ * @v iobuf            Datagram I/O buffer
+ * @v meta             Data transfer metadata
+ * @ret rc             Return status code
+ */
+static int srpdev_deliver ( struct srp_device *srpdev,
+                           struct io_buffer *iobuf,
+                           struct xfer_metadata *meta __unused ) {
+       struct srp_common *common = iobuf->data;
+       int ( * type ) ( struct srp_device *srp, const void *data, size_t len );
+       int rc;
+
+       /* Sanity check */
+       if ( iob_len ( iobuf ) < sizeof ( *common ) ) {
+               DBGC ( srpdev, "SRP %p IU too short (%zd bytes)\n",
+                      srpdev, iob_len ( iobuf ) );
+               rc = -EINVAL;
+               goto err;
+       }
+
+       /* Determine IU type */
+       switch ( common->type ) {
+       case SRP_LOGIN_RSP:
+               type = srp_login_rsp;
+               break;
+       case SRP_LOGIN_REJ:
+               type = srp_login_rej;
+               break;
+       case SRP_RSP:
+               type = srp_rsp;
+               break;
+       default:
+               type = srp_unrecognised;
+               break;
+       }
+
+       /* Handle IU */
+       if ( ( rc = type ( srpdev, iobuf->data, iob_len ( iobuf ) ) ) != 0 )
+               goto err;
+
+       free_iob ( iobuf );
+       return 0;
+
+ err:
+       DBGC ( srpdev, "SRP %p closing due to received IU (%s):\n",
+              srpdev, strerror ( rc ) );
+       DBGC_HDA ( srpdev, 0, iobuf->data, iob_len ( iobuf ) );
+       free_iob ( iobuf );
+       srpdev_close ( srpdev, rc );
+       return rc;
+}
+
+/**
+ * Check SRP device flow-control window
+ *
+ * @v srpdev           SRP device
+ * @ret len            Length of window
+ */
+static size_t srpdev_window ( struct srp_device *srpdev ) {
+       return ( srpdev->logged_in ? ~( ( size_t ) 0 ) : 0 );
+}
+
+/**
+ * A (transport-independent) sBFT created by iPXE
+ */
+struct ipxe_sbft {
+       /** The table header */
+       struct sbft_table table;
+       /** The SCSI subtable */
+       struct sbft_scsi_subtable scsi;
+       /** The SRP subtable */
+       struct sbft_srp_subtable srp;
+} __attribute__ (( packed, aligned ( 16 ) ));
+
+/**
+ * Describe SRP device in an ACPI table
+ *
+ * @v srpdev           SRP device
+ * @v acpi             ACPI table
+ * @v len              Length of ACPI table
+ * @ret rc             Return status code
+ */
+static int srpdev_describe ( struct srp_device *srpdev,
+                            struct acpi_description_header *acpi,
+                            size_t len ) {
+       struct ipxe_sbft *sbft =
+               container_of ( acpi, struct ipxe_sbft, table.acpi );
+       int rc;
+
+       /* Sanity check */
+       if ( len < sizeof ( *sbft ) )
+               return -ENOBUFS;
+
+       /* Populate table */
+       sbft->table.acpi.signature = cpu_to_le32 ( SBFT_SIG );
+       sbft->table.acpi.length = cpu_to_le32 ( sizeof ( *sbft ) );
+       sbft->table.acpi.revision = 1;
+       sbft->table.scsi_offset =
+               cpu_to_le16 ( offsetof ( typeof ( *sbft ), scsi ) );
+       memcpy ( &sbft->scsi.lun, &srpdev->lun, sizeof ( sbft->scsi.lun ) );
+       sbft->table.srp_offset =
+               cpu_to_le16 ( offsetof ( typeof ( *sbft ), srp ) );
+       memcpy ( &sbft->srp.initiator, &srpdev->initiator,
+                sizeof ( sbft->srp.initiator ) );
+       memcpy ( &sbft->srp.target, &srpdev->target,
+                sizeof ( sbft->srp.target ) );
+
+       /* Ask transport layer to describe transport-specific portions */
+       if ( ( rc = acpi_describe ( &srpdev->socket, acpi, len ) ) != 0 ) {
+               DBGC ( srpdev, "SRP %p cannot describe transport layer: %s\n",
+                      srpdev, strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/** SRP device socket interface operations */
+static struct interface_operation srpdev_socket_op[] = {
+       INTF_OP ( xfer_deliver, struct srp_device *, srpdev_deliver ),
+       INTF_OP ( intf_close, struct srp_device *, srpdev_close ),
+};
+
+/** SRP device socket interface descriptor */
+static struct interface_descriptor srpdev_socket_desc =
+       INTF_DESC_PASSTHRU ( struct srp_device, socket, srpdev_socket_op,
+                            scsi );
+
+/** SRP device SCSI interface operations */
+static struct interface_operation srpdev_scsi_op[] = {
+       INTF_OP ( scsi_command, struct srp_device *, srpdev_scsi_command ),
+       INTF_OP ( xfer_window, struct srp_device *, srpdev_window ),
+       INTF_OP ( intf_close, struct srp_device *, srpdev_close ),
+       INTF_OP ( acpi_describe, struct srp_device *, srpdev_describe ),
+};
+
+/** SRP device SCSI interface descriptor */
+static struct interface_descriptor srpdev_scsi_desc =
+       INTF_DESC_PASSTHRU ( struct srp_device, scsi, srpdev_scsi_op, socket  );
+
+/**
+ * Open SRP device
+ *
+ * @v block            Block control interface
+ * @v socket           Socket interface
+ * @v initiator                Initiator port ID
+ * @v target           Target port ID
+ * @v memory_handle    RDMA memory handle
+ * @v lun              SCSI LUN
+ * @ret rc             Return status code
+ */
+int srp_open ( struct interface *block, struct interface *socket,
+              union srp_port_id *initiator, union srp_port_id *target,
+              uint32_t memory_handle, struct scsi_lun *lun ) {
+       struct srp_device *srpdev;
+       int tag;
+       int rc;
+
+       /* Allocate and initialise structure */
+       srpdev = zalloc ( sizeof ( *srpdev ) );
+       if ( ! srpdev ) {
+               rc = -ENOMEM;
+               goto err_zalloc;
+       }
+       ref_init ( &srpdev->refcnt, NULL );
+       intf_init ( &srpdev->scsi, &srpdev_scsi_desc, &srpdev->refcnt );
+       intf_init ( &srpdev->socket, &srpdev_socket_desc, &srpdev->refcnt );
+       INIT_LIST_HEAD ( &srpdev->commands );
+       srpdev->memory_handle = memory_handle;
+       DBGC ( srpdev, "SRP %p %08x%08x%08x%08x->%08x%08x%08x%08x\n", srpdev,
+              ntohl ( initiator->dwords[0] ), ntohl ( initiator->dwords[1] ),
+              ntohl ( initiator->dwords[2] ), ntohl ( initiator->dwords[3] ),
+              ntohl ( target->dwords[0] ), ntohl ( target->dwords[1] ),
+              ntohl ( target->dwords[2] ), ntohl ( target->dwords[3] ) );
+
+       /* Preserve parameters required for boot firmware table */
+       memcpy ( &srpdev->initiator, initiator, sizeof ( srpdev->initiator ) );
+       memcpy ( &srpdev->target, target, sizeof ( srpdev->target ) );
+       memcpy ( &srpdev->lun, lun, sizeof ( srpdev->lun ) );
+
+       /* Attach to socket interface and initiate login */
+       intf_plug_plug ( &srpdev->socket, socket );
+       tag = srp_new_tag ( srpdev );
+       assert ( tag >= 0 ); /* Cannot fail when no commands in progress */
+       if ( ( rc = srp_login ( srpdev, initiator, target, tag ) ) != 0 )
+               goto err_login;
+
+       /* Attach SCSI device to parent interface */
+       if ( ( rc = scsi_open ( block, &srpdev->scsi, lun ) ) != 0 ) {
+               DBGC ( srpdev, "SRP %p could not create SCSI device: %s\n",
+                      srpdev, strerror ( rc ) );
+               goto err_scsi_open;
+       }
+
+       /* Mortalise self and return */
+       ref_put ( &srpdev->refcnt );
+       return 0;
+
+ err_scsi_open:
+ err_login:
+       srpdev_close ( srpdev, rc );
+       ref_put ( &srpdev->refcnt );
+ err_zalloc:
+       return rc;
+}