Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / ipxe / src / net / fcp.c
diff --git a/qemu/roms/ipxe/src/net/fcp.c b/qemu/roms/ipxe/src/net/fcp.c
new file mode 100644 (file)
index 0000000..9c36a4c
--- /dev/null
@@ -0,0 +1,1092 @@
+/*
+ * Copyright (C) 2010 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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <assert.h>
+#include <byteswap.h>
+#include <ipxe/refcnt.h>
+#include <ipxe/list.h>
+#include <ipxe/interface.h>
+#include <ipxe/xfer.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/open.h>
+#include <ipxe/process.h>
+#include <ipxe/uri.h>
+#include <ipxe/acpi.h>
+#include <ipxe/scsi.h>
+#include <ipxe/device.h>
+#include <ipxe/edd.h>
+#include <ipxe/fc.h>
+#include <ipxe/fcels.h>
+#include <ipxe/fcp.h>
+
+/** @file
+ *
+ * Fibre Channel Protocol
+ *
+ */
+
+/* Disambiguate the various error causes */
+#define ERANGE_READ_DATA_ORDERING \
+       __einfo_error ( EINFO_ERANGE_READ_DATA_ORDERING )
+#define EINFO_ERANGE_READ_DATA_ORDERING \
+       __einfo_uniqify ( EINFO_ERANGE, 0x01, "Read data out of order" )
+#define ERANGE_READ_DATA_OVERRUN \
+       __einfo_error ( EINFO_ERANGE_READ_DATA_OVERRUN )
+#define EINFO_ERANGE_READ_DATA_OVERRUN \
+       __einfo_uniqify ( EINFO_ERANGE, 0x02, "Read data overrun" )
+#define ERANGE_WRITE_DATA_STUCK \
+       __einfo_error ( EINFO_ERANGE_WRITE_DATA_STUCK )
+#define EINFO_ERANGE_WRITE_DATA_STUCK \
+       __einfo_uniqify ( EINFO_ERANGE, 0x03, "Write data stuck" )
+#define ERANGE_WRITE_DATA_OVERRUN \
+       __einfo_error ( EINFO_ERANGE_WRITE_DATA_OVERRUN )
+#define EINFO_ERANGE_WRITE_DATA_OVERRUN \
+       __einfo_uniqify ( EINFO_ERANGE, 0x04, "Write data overrun" )
+#define ERANGE_DATA_UNDERRUN \
+       __einfo_error ( EINFO_ERANGE_DATA_UNDERRUN )
+#define EINFO_ERANGE_DATA_UNDERRUN \
+       __einfo_uniqify ( EINFO_ERANGE, 0x05, "Data underrun" )
+
+/******************************************************************************
+ *
+ * PRLI
+ *
+ ******************************************************************************
+ */
+
+struct fc_els_prli_descriptor fcp_prli_descriptor __fc_els_prli_descriptor;
+
+/**
+ * Transmit FCP PRLI
+ *
+ * @v els              Fibre Channel ELS transaction
+ * @ret rc             Return status code
+ */
+static int fcp_prli_tx ( struct fc_els *els ) {
+       struct fcp_prli_service_parameters param;
+
+       /* Build service parameter page */
+       memset ( &param, 0, sizeof ( param ) );
+       param.flags = htonl ( FCP_PRLI_NO_READ_RDY | FCP_PRLI_INITIATOR );
+
+       return fc_els_prli_tx ( els, &fcp_prli_descriptor, &param );
+}
+
+/**
+ * Receive FCP PRLI
+ *
+ * @v els              Fibre Channel ELS transaction
+ * @v frame            ELS frame
+ * @v len              Length of ELS frame
+ * @ret rc             Return status code
+ */
+static int fcp_prli_rx ( struct fc_els *els, void *data, size_t len ) {
+       return fc_els_prli_rx ( els, &fcp_prli_descriptor, data, len );
+}
+
+/**
+ * Detect FCP PRLI
+ *
+ * @v els              Fibre Channel ELS transaction
+ * @v data             ELS frame
+ * @v len              Length of ELS frame
+ * @ret rc             Return status code
+ */
+static int fcp_prli_detect ( struct fc_els *els, const void *data,
+                            size_t len ) {
+       return fc_els_prli_detect ( els, &fcp_prli_descriptor, data, len );
+}
+
+/** FCP PRLI ELS handler */
+struct fc_els_handler fcp_prli_handler __fc_els_handler = {
+       .name           = "PRLI-FCP",
+       .tx             = fcp_prli_tx,
+       .rx             = fcp_prli_rx,
+       .detect         = fcp_prli_detect,
+};
+
+/** FCP PRLI descriptor */
+struct fc_els_prli_descriptor fcp_prli_descriptor __fc_els_prli_descriptor = {
+       .type           = FC_TYPE_FCP,
+       .param_len      = sizeof ( struct fcp_prli_service_parameters ),
+       .handler        = &fcp_prli_handler,
+};
+
+/******************************************************************************
+ *
+ * FCP devices and commands
+ *
+ ******************************************************************************
+ */
+
+/** An FCP device */
+struct fcp_device {
+       /** Reference count */
+       struct refcnt refcnt;
+       /** Fibre Channel upper-layer protocol user */
+       struct fc_ulp_user user;
+       /** SCSI command issuing interface */
+       struct interface scsi;
+       /** List of active commands */
+       struct list_head fcpcmds;
+
+       /** Fibre Channel WWN (for boot firmware table) */
+       struct fc_name wwn;
+       /** SCSI LUN (for boot firmware table) */
+       struct scsi_lun lun;
+};
+
+/** An FCP command */
+struct fcp_command {
+       /** Reference count */
+       struct refcnt refcnt;
+       /** FCP SCSI device */
+       struct fcp_device *fcpdev;
+       /** List of active commands */
+       struct list_head list;
+       /** SCSI command interface */
+       struct interface scsi;
+       /** Fibre Channel exchange interface */
+       struct interface xchg;
+       /** Send process */
+       struct process process;
+       /** Send current IU
+        *
+        * @v fcpcmd    FCP command
+        * @ret rc      Return status code
+        */
+       int ( * send ) ( struct fcp_command *fcpcmd );
+       /** SCSI command */
+       struct scsi_cmd command;
+       /** Data offset within command */
+       size_t offset;
+       /** Length of data remaining to be sent within this IU */
+       size_t remaining;
+       /** Exchange ID */
+       uint16_t xchg_id;
+};
+
+/**
+ * Get reference to FCP device
+ *
+ * @v fcpdev           FCP device
+ * @ret fcpdev         FCP device
+ */
+static inline __attribute__ (( always_inline )) struct fcp_device *
+fcpdev_get ( struct fcp_device *fcpdev ) {
+       ref_get ( &fcpdev->refcnt );
+       return fcpdev;
+}
+
+/**
+ * Drop reference to FCP device
+ *
+ * @v fcpdev           FCP device
+ */
+static inline __attribute__ (( always_inline )) void
+fcpdev_put ( struct fcp_device *fcpdev ) {
+       ref_put ( &fcpdev->refcnt );
+}
+
+/**
+ * Get reference to FCP command
+ *
+ * @v fcpcmd           FCP command
+ * @ret fcpcmd         FCP command
+ */
+static inline __attribute__ (( always_inline )) struct fcp_command *
+fcpcmd_get ( struct fcp_command *fcpcmd ) {
+       ref_get ( &fcpcmd->refcnt );
+       return fcpcmd;
+}
+
+/**
+ * Drop reference to FCP command
+ *
+ * @v fcpcmd           FCP command
+ */
+static inline __attribute__ (( always_inline )) void
+fcpcmd_put ( struct fcp_command *fcpcmd ) {
+       ref_put ( &fcpcmd->refcnt );
+}
+
+/**
+ * Start FCP command sending
+ *
+ * @v fcpcmd           FCP command
+ * @v send             Send method
+ */
+static inline __attribute__ (( always_inline )) void
+fcpcmd_start_send ( struct fcp_command *fcpcmd,
+                   int ( * send ) ( struct fcp_command *fcpcmd ) ) {
+       fcpcmd->send = send;
+       process_add ( &fcpcmd->process );
+}
+
+/**
+ * Stop FCP command sending
+ *
+ * @v fcpcmd           FCP command
+ */
+static inline __attribute__ (( always_inline )) void
+fcpcmd_stop_send ( struct fcp_command *fcpcmd ) {
+       process_del ( &fcpcmd->process );
+}
+
+/**
+ * Free FCP command
+ *
+ * @v refcnt           Reference count
+ */
+static void fcpcmd_free ( struct refcnt *refcnt ) {
+       struct fcp_command *fcpcmd =
+               container_of ( refcnt, struct fcp_command, refcnt );
+
+       /* Remove from list of commands */
+       list_del ( &fcpcmd->list );
+       fcpdev_put ( fcpcmd->fcpdev );
+
+       /* Free command */
+       free ( fcpcmd );
+}
+
+/**
+ * Close FCP command
+ *
+ * @v fcpcmd           FCP command
+ * @v rc               Reason for close
+ */
+static void fcpcmd_close ( struct fcp_command *fcpcmd, int rc ) {
+       struct fcp_device *fcpdev = fcpcmd->fcpdev;
+
+       if ( rc != 0 ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x closed: %s\n",
+                      fcpdev, fcpcmd->xchg_id, strerror ( rc ) );
+       }
+
+       /* Stop sending */
+       fcpcmd_stop_send ( fcpcmd );
+
+       /* Shut down interfaces */
+       intf_shutdown ( &fcpcmd->scsi, rc );
+       intf_shutdown ( &fcpcmd->xchg, rc );
+}
+
+/**
+ * Close FCP command in error
+ *
+ * @v fcpcmd           FCP command
+ * @v rc               Reason for close
+ */
+static void fcpcmd_close_err ( struct fcp_command *fcpcmd, int rc ) {
+       if ( rc == 0 )
+               rc = -EPIPE;
+       fcpcmd_close ( fcpcmd, rc );
+}
+
+/**
+ * Send FCP command IU
+ *
+ * @v fcpcmd           FCP command
+ * @ret rc             Return status code
+ */
+static int fcpcmd_send_cmnd ( struct fcp_command *fcpcmd ) {
+       struct fcp_device *fcpdev = fcpcmd->fcpdev;
+       struct scsi_cmd *command = &fcpcmd->command;
+       struct io_buffer *iobuf;
+       struct fcp_cmnd *cmnd;
+       struct xfer_metadata meta;
+       int rc;
+
+       /* Sanity check */
+       if ( command->data_in_len && command->data_out_len ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x cannot handle bidirectional "
+                      "command\n", fcpdev, fcpcmd->xchg_id );
+               return -ENOTSUP;
+       }
+
+       /* Allocate I/O buffer */
+       iobuf = xfer_alloc_iob ( &fcpcmd->xchg, sizeof ( *cmnd ) );
+       if ( ! iobuf ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x cannot allocate command IU\n",
+                      fcpdev, fcpcmd->xchg_id );
+               return -ENOMEM;
+       }
+
+       /* Construct command IU frame */
+       cmnd = iob_put ( iobuf, sizeof ( *cmnd ) );
+       memset ( cmnd, 0, sizeof ( *cmnd ) );
+       memcpy ( &cmnd->lun, &command->lun, sizeof ( cmnd->lun ) );
+       assert ( ! ( command->data_in_len && command->data_out_len ) );
+       if ( command->data_in_len )
+               cmnd->dirn |= FCP_CMND_RDDATA;
+       if ( command->data_out_len )
+               cmnd->dirn |= FCP_CMND_WRDATA;
+       memcpy ( &cmnd->cdb, &fcpcmd->command.cdb, sizeof ( cmnd->cdb ) );
+       cmnd->len = htonl ( command->data_in_len + command->data_out_len );
+       memset ( &meta, 0, sizeof ( meta ) );
+       meta.flags = ( XFER_FL_CMD_STAT | XFER_FL_OVER );
+       DBGC2 ( fcpdev, "FCP %p xchg %04x CMND " SCSI_CDB_FORMAT " %04x\n",
+               fcpdev, fcpcmd->xchg_id, SCSI_CDB_DATA ( cmnd->cdb ),
+               ntohl ( cmnd->len ) );
+
+       /* No further data to send within this IU */
+       fcpcmd_stop_send ( fcpcmd );
+
+       /* Send command IU frame */
+       if ( ( rc = xfer_deliver ( &fcpcmd->xchg, iob_disown ( iobuf ),
+                                  &meta ) ) != 0 ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x cannot deliver command IU: "
+                      "%s\n", fcpdev, fcpcmd->xchg_id, strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Handle FCP read data IU
+ *
+ * @v fcpcmd           FCP command
+ * @v iobuf            I/O buffer
+ * @v meta             Data transfer metadata
+ * @ret rc             Return status code
+ */
+static int fcpcmd_recv_rddata ( struct fcp_command *fcpcmd,
+                               struct io_buffer *iobuf,
+                               struct xfer_metadata *meta ) {
+       struct fcp_device *fcpdev = fcpcmd->fcpdev;
+       struct scsi_cmd *command = &fcpcmd->command;
+       size_t offset = meta->offset;
+       size_t len = iob_len ( iobuf );
+       int rc;
+
+       /* Sanity checks */
+       if ( ! ( meta->flags & XFER_FL_ABS_OFFSET ) ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x read data missing offset\n",
+                      fcpdev, fcpcmd->xchg_id );
+               rc = -ERANGE_READ_DATA_ORDERING;
+               goto done;
+       }
+       if ( offset != fcpcmd->offset ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x read data out of order "
+                      "(expected %zd, received %zd)\n",
+                      fcpdev, fcpcmd->xchg_id, fcpcmd->offset, offset );
+               rc = -ERANGE_READ_DATA_ORDERING;
+               goto done;
+       }
+       if ( ( offset + len ) > command->data_in_len ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x read data overrun (max %zd, "
+                      "received %zd)\n", fcpdev, fcpcmd->xchg_id,
+                      command->data_in_len, ( offset + len ) );
+               rc = -ERANGE_READ_DATA_OVERRUN;
+               goto done;
+       }
+       DBGC2 ( fcpdev, "FCP %p xchg %04x RDDATA [%08zx,%08zx)\n",
+               fcpdev, fcpcmd->xchg_id, offset, ( offset + len ) );
+
+       /* Copy to user buffer */
+       copy_to_user ( command->data_in, offset, iobuf->data, len );
+       fcpcmd->offset += len;
+       assert ( fcpcmd->offset <= command->data_in_len );
+
+       rc = 0;
+ done:
+       free_iob ( iobuf );
+       return rc;
+}
+
+/**
+ * Send FCP write data IU
+ *
+ * @v fcpcmd           FCP command
+ * @ret rc             Return status code
+ */
+static int fcpcmd_send_wrdata ( struct fcp_command *fcpcmd ) {
+       struct fcp_device *fcpdev = fcpcmd->fcpdev;
+       struct scsi_cmd *command = &fcpcmd->command;
+       struct io_buffer *iobuf;
+       struct xfer_metadata meta;
+       size_t len;
+       int rc;
+
+       /* Calculate length to be sent */
+       len = xfer_window ( &fcpcmd->xchg );
+       if ( len > fcpcmd->remaining )
+               len = fcpcmd->remaining;
+
+       /* Sanity checks */
+       if ( len == 0 ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x write data stuck\n",
+                      fcpdev, fcpcmd->xchg_id );
+               return -ERANGE_WRITE_DATA_STUCK;
+       }
+       if ( ( fcpcmd->offset + len ) > command->data_out_len ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x write data overrun (max %zd, "
+                      "requested %zd)\n", fcpdev, fcpcmd->xchg_id,
+                      command->data_out_len, ( fcpcmd->offset + len ) );
+               return -ERANGE_WRITE_DATA_OVERRUN;
+       }
+
+       /* Allocate I/O buffer */
+       iobuf = xfer_alloc_iob ( &fcpcmd->xchg, len );
+       if ( ! iobuf ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x cannot allocate write data "
+                      "IU for %zd bytes\n", fcpdev, fcpcmd->xchg_id, len );
+               return -ENOMEM;
+       }
+
+       /* Construct data IU frame */
+       copy_from_user ( iob_put ( iobuf, len ), command->data_out,
+                        fcpcmd->offset, len );
+       memset ( &meta, 0, sizeof ( meta ) );
+       meta.flags = ( XFER_FL_RESPONSE | XFER_FL_ABS_OFFSET );
+       meta.offset = fcpcmd->offset;
+       DBGC2 ( fcpdev, "FCP %p xchg %04x WRDATA [%08zx,%04zx)\n",
+               fcpdev, fcpcmd->xchg_id, fcpcmd->offset,
+               ( fcpcmd->offset + iob_len ( iobuf ) ) );
+
+       /* Calculate amount of data remaining to be sent within this IU */
+       assert ( len <= fcpcmd->remaining );
+       fcpcmd->offset += len;
+       fcpcmd->remaining -= len;
+       assert ( fcpcmd->offset <= command->data_out_len );
+       if ( fcpcmd->remaining == 0 ) {
+               fcpcmd_stop_send ( fcpcmd );
+               meta.flags |= XFER_FL_OVER;
+       }
+
+       /* Send data IU frame */
+       if ( ( rc = xfer_deliver ( &fcpcmd->xchg, iob_disown ( iobuf ),
+                                  &meta ) ) != 0 ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x cannot deliver write data "
+                      "IU: %s\n", fcpdev, fcpcmd->xchg_id, strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Handle FCP transfer ready IU
+ *
+ * @v fcpcmd           FCP command
+ * @v iobuf            I/O buffer
+ * @v meta             Data transfer metadata
+ * @ret rc             Return status code
+ */
+static int fcpcmd_recv_xfer_rdy ( struct fcp_command *fcpcmd,
+                                 struct io_buffer *iobuf,
+                                 struct xfer_metadata *meta __unused ) {
+       struct fcp_device *fcpdev = fcpcmd->fcpdev;
+       struct fcp_xfer_rdy *xfer_rdy = iobuf->data;
+       int rc;
+
+       /* Sanity checks */
+       if ( iob_len ( iobuf ) != sizeof ( *xfer_rdy ) ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x received invalid transfer "
+                      "ready IU:\n", fcpdev, fcpcmd->xchg_id );
+               DBGC_HDA ( fcpdev, 0, iobuf->data, iob_len ( iobuf ) );
+               rc = -EPROTO;
+               goto done;
+       }
+       if ( ntohl ( xfer_rdy->offset ) != fcpcmd->offset ) {
+               /* We do not advertise out-of-order delivery */
+               DBGC ( fcpdev, "FCP %p xchg %04x cannot support out-of-order "
+                      "delivery (expected %zd, requested %d)\n",
+                      fcpdev, fcpcmd->xchg_id, fcpcmd->offset,
+                      ntohl ( xfer_rdy->offset ) );
+               rc = -EPROTO;
+               goto done;
+       }
+       DBGC2 ( fcpdev, "FCP %p xchg %04x XFER_RDY [%08x,%08x)\n",
+               fcpdev, fcpcmd->xchg_id, ntohl ( xfer_rdy->offset ),
+               ( ntohl ( xfer_rdy->offset ) + ntohl ( xfer_rdy->len ) ) );
+
+       /* Start sending requested data */
+       fcpcmd->remaining = ntohl ( xfer_rdy->len );
+       fcpcmd_start_send ( fcpcmd, fcpcmd_send_wrdata );
+
+       rc = 0;
+ done:
+       free_iob ( iobuf );
+       return rc;
+}
+
+/**
+ * Handle FCP response IU
+ *
+ * @v fcpcmd           FCP command
+ * @v iobuf            I/O buffer
+ * @v meta             Data transfer metadata
+ * @ret rc             Return status code
+ */
+static int fcpcmd_recv_rsp ( struct fcp_command *fcpcmd,
+                            struct io_buffer *iobuf,
+                            struct xfer_metadata *meta __unused ) {
+       struct fcp_device *fcpdev = fcpcmd->fcpdev;
+       struct scsi_cmd *command = &fcpcmd->command;
+       struct fcp_rsp *rsp = iobuf->data;
+       struct scsi_rsp response;
+       int rc;
+
+       /* Sanity check */
+       if ( ( iob_len ( iobuf ) < sizeof ( *rsp ) ) ||
+            ( iob_len ( iobuf ) < ( sizeof ( *rsp ) +
+                                    fcp_rsp_response_data_len ( rsp ) +
+                                    fcp_rsp_sense_data_len ( rsp ) ) ) ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x received invalid response "
+                      "IU:\n", fcpdev, fcpcmd->xchg_id );
+               DBGC_HDA ( fcpdev, 0, iobuf->data, iob_len ( iobuf ) );
+               rc = -EPROTO;
+               goto done;
+       }
+       DBGC2 ( fcpdev, "FCP %p xchg %04x RSP stat %02x resid %08x flags %02x"
+               "%s%s%s%s\n", fcpdev, fcpcmd->xchg_id, rsp->status,
+               ntohl ( rsp->residual ), rsp->flags,
+               ( ( rsp->flags & FCP_RSP_RESPONSE_LEN_VALID ) ? " resp" : "" ),
+               ( ( rsp->flags & FCP_RSP_SENSE_LEN_VALID ) ? " sense" : "" ),
+               ( ( rsp->flags & FCP_RSP_RESIDUAL_OVERRUN ) ? " over" : "" ),
+               ( ( rsp->flags & FCP_RSP_RESIDUAL_UNDERRUN ) ? " under" : "" ));
+       if ( fcp_rsp_response_data ( rsp ) ) {
+               DBGC2 ( fcpdev, "FCP %p xchg %04x response data:\n",
+                       fcpdev, fcpcmd->xchg_id );
+               DBGC2_HDA ( fcpdev, 0, fcp_rsp_response_data ( rsp ),
+                           fcp_rsp_response_data_len ( rsp ) );
+       }
+       if ( fcp_rsp_sense_data ( rsp ) ) {
+               DBGC2 ( fcpdev, "FCP %p xchg %04x sense data:\n",
+                       fcpdev, fcpcmd->xchg_id );
+               DBGC2_HDA ( fcpdev, 0, fcp_rsp_sense_data ( rsp ),
+                           fcp_rsp_sense_data_len ( rsp ) );
+       }
+
+       /* Check for locally-detected command underrun */
+       if ( ( rsp->status == 0 ) &&
+            ( fcpcmd->offset != ( command->data_in_len +
+                                  command->data_out_len ) ) ) {
+               DBGC ( fcpdev, "FCP %p xchg %04x data underrun (expected %zd, "
+                      "got %zd)\n", fcpdev, fcpcmd->xchg_id,
+                      ( command->data_in_len + command->data_out_len ),
+                      fcpcmd->offset );
+               rc = -ERANGE_DATA_UNDERRUN;
+               goto done;
+       }
+
+       /* Build SCSI response */
+       memset ( &response, 0, sizeof ( response ) );
+       response.status = rsp->status;
+       if ( rsp->flags & ( FCP_RSP_RESIDUAL_OVERRUN |
+                           FCP_RSP_RESIDUAL_UNDERRUN ) ) {
+               response.overrun = ntohl ( rsp->residual );
+               if ( rsp->flags & FCP_RSP_RESIDUAL_UNDERRUN )
+                       response.overrun = -response.overrun;
+       }
+       scsi_parse_sense ( fcp_rsp_sense_data ( rsp ),
+                          fcp_rsp_sense_data_len ( rsp ), &response.sense );
+
+       /* Free buffer before sending response, to minimise
+        * out-of-memory errors.
+        */
+       free_iob ( iob_disown ( iobuf ) );
+
+       /* Send SCSI response */
+       scsi_response ( &fcpcmd->scsi, &response );
+
+       /* Terminate command */
+       fcpcmd_close ( fcpcmd, 0 );
+
+       rc = 0;
+ done:
+       free_iob ( iobuf );
+       return rc;
+}
+
+/**
+ * Handle unknown FCP IU
+ *
+ * @v fcpcmd           FCP command
+ * @v iobuf            I/O buffer
+ * @v meta             Data transfer metadata
+ * @ret rc             Return status code
+ */
+static int fcpcmd_recv_unknown ( struct fcp_command *fcpcmd,
+                                struct io_buffer *iobuf,
+                                struct xfer_metadata *meta __unused ) {
+       struct fcp_device *fcpdev = fcpcmd->fcpdev;
+
+       DBGC ( fcpdev, "FCP %p xchg %04x received unknown IU:\n",
+              fcpdev, fcpcmd->xchg_id );
+       DBGC_HDA ( fcpdev, 0, iobuf->data, iob_len ( iobuf ) );
+       free_iob ( iobuf );
+       return -EPROTO;
+}
+
+/**
+ * Transmit FCP frame
+ *
+ * @v fcpcmd           FCP command
+ */
+static void fcpcmd_step ( struct fcp_command *fcpcmd ) {
+       int rc;
+
+       /* Send the current IU */
+       if ( ( rc = fcpcmd->send ( fcpcmd ) ) != 0 ) {
+               /* Treat failure as a fatal error */
+               fcpcmd_close ( fcpcmd, rc );
+       }
+}
+
+/**
+ * Receive FCP frame
+ *
+ * @v fcpcmd           FCP command
+ * @v iobuf            I/O buffer
+ * @v meta             Data transfer metadata
+ * @ret rc             Return status code
+ */
+static int fcpcmd_deliver ( struct fcp_command *fcpcmd,
+                           struct io_buffer *iobuf,
+                           struct xfer_metadata *meta ) {
+       int ( * fcpcmd_recv ) ( struct fcp_command *fcpcmd,
+                               struct io_buffer *iobuf,
+                               struct xfer_metadata *meta );
+       int rc;
+
+       /* Determine handler */
+       switch ( meta->flags & ( XFER_FL_CMD_STAT | XFER_FL_RESPONSE ) ) {
+       case ( XFER_FL_RESPONSE ) :
+               fcpcmd_recv = fcpcmd_recv_rddata;
+               break;
+       case ( XFER_FL_CMD_STAT ) :
+               fcpcmd_recv = fcpcmd_recv_xfer_rdy;
+               break;
+       case ( XFER_FL_CMD_STAT | XFER_FL_RESPONSE ) :
+               fcpcmd_recv = fcpcmd_recv_rsp;
+               break;
+       default:
+               fcpcmd_recv = fcpcmd_recv_unknown;
+               break;
+       }
+
+       /* Handle IU */
+       if ( ( rc = fcpcmd_recv ( fcpcmd, iob_disown ( iobuf ), meta ) ) != 0 ){
+               /* Treat any error as fatal to the command */
+               fcpcmd_close ( fcpcmd, rc );
+       }
+
+       return rc;
+}
+
+/** FCP command SCSI interface operations */
+static struct interface_operation fcpcmd_scsi_op[] = {
+       INTF_OP ( intf_close, struct fcp_command *, fcpcmd_close ),
+};
+
+/** FCP command SCSI interface descriptor */
+static struct interface_descriptor fcpcmd_scsi_desc =
+       INTF_DESC_PASSTHRU ( struct fcp_command, scsi, fcpcmd_scsi_op, xchg );
+
+/** FCP command Fibre Channel exchange interface operations */
+static struct interface_operation fcpcmd_xchg_op[] = {
+       INTF_OP ( xfer_deliver, struct fcp_command *, fcpcmd_deliver ),
+       INTF_OP ( intf_close, struct fcp_command *, fcpcmd_close_err ),
+};
+
+/** FCP command Fibre Channel exchange interface descriptor */
+static struct interface_descriptor fcpcmd_xchg_desc =
+       INTF_DESC_PASSTHRU ( struct fcp_command, xchg, fcpcmd_xchg_op, scsi );
+
+/** FCP command process descriptor */
+static struct process_descriptor fcpcmd_process_desc =
+       PROC_DESC ( struct fcp_command, process, fcpcmd_step );
+
+/**
+ * Issue FCP SCSI command
+ *
+ * @v fcpdev           FCP device
+ * @v parent           Parent interface
+ * @v command          SCSI command
+ * @ret tag            Command tag, or negative error
+ */
+static int fcpdev_scsi_command ( struct fcp_device *fcpdev,
+                                struct interface *parent,
+                                struct scsi_cmd *command ) {
+       struct fcp_prli_service_parameters *param = fcpdev->user.ulp->param;
+       struct fcp_command *fcpcmd;
+       int xchg_id;
+       int rc;
+
+       /* Check link */
+       if ( ( rc = fcpdev->user.ulp->link.rc ) != 0 ) {
+               DBGC ( fcpdev, "FCP %p could not issue command while link is "
+                      "down: %s\n", fcpdev, strerror ( rc ) );
+               goto err_link;
+       }
+
+       /* Check target capability */
+       assert ( param != NULL );
+       assert ( fcpdev->user.ulp->param_len >= sizeof ( *param ) );
+       if ( ! ( param->flags & htonl ( FCP_PRLI_TARGET ) ) ) {
+               DBGC ( fcpdev, "FCP %p could not issue command: not a target\n",
+                      fcpdev );
+               rc = -ENOTTY;
+               goto err_target;
+       }
+
+       /* Allocate and initialise structure */
+       fcpcmd = zalloc ( sizeof ( *fcpcmd ) );
+       if ( ! fcpcmd ) {
+               rc = -ENOMEM;
+               goto err_zalloc;
+       }
+       ref_init ( &fcpcmd->refcnt, fcpcmd_free );
+       intf_init ( &fcpcmd->scsi, &fcpcmd_scsi_desc, &fcpcmd->refcnt );
+       intf_init ( &fcpcmd->xchg, &fcpcmd_xchg_desc, &fcpcmd->refcnt );
+       process_init_stopped ( &fcpcmd->process, &fcpcmd_process_desc,
+                              &fcpcmd->refcnt );
+       fcpcmd->fcpdev = fcpdev_get ( fcpdev );
+       list_add ( &fcpcmd->list, &fcpdev->fcpcmds );
+       memcpy ( &fcpcmd->command, command, sizeof ( fcpcmd->command ) );
+
+       /* Create new exchange */
+       if ( ( xchg_id = fc_xchg_originate ( &fcpcmd->xchg,
+                                            fcpdev->user.ulp->peer->port,
+                                            &fcpdev->user.ulp->peer->port_id,
+                                            FC_TYPE_FCP ) ) < 0 ) {
+               rc = xchg_id;
+               DBGC ( fcpdev, "FCP %p could not create exchange: %s\n",
+                      fcpdev, strerror ( rc ) );
+               goto err_xchg_originate;
+       }
+       fcpcmd->xchg_id = xchg_id;
+
+       /* Start sending command IU */
+       fcpcmd_start_send ( fcpcmd, fcpcmd_send_cmnd );
+
+       /* Attach to parent interface, mortalise self, and return */
+       intf_plug_plug ( &fcpcmd->scsi, parent );
+       ref_put ( &fcpcmd->refcnt );
+       return ( FCP_TAG_MAGIC | fcpcmd->xchg_id );
+
+ err_xchg_originate:
+       fcpcmd_close ( fcpcmd, rc );
+       ref_put ( &fcpcmd->refcnt );
+ err_zalloc:
+ err_target:
+ err_link:
+       return rc;
+}
+
+/**
+ * Close FCP device
+ *
+ * @v fcpdev           FCP device
+ * @v rc               Reason for close
+ */
+static void fcpdev_close ( struct fcp_device *fcpdev, int rc ) {
+       struct fcp_command *fcpcmd;
+       struct fcp_command *tmp;
+
+       DBGC ( fcpdev, "FCP %p closed: %s\n", fcpdev, strerror ( rc ) );
+
+       /* Shut down interfaces */
+       intf_shutdown ( &fcpdev->scsi, rc );
+
+       /* Shut down any active commands */
+       list_for_each_entry_safe ( fcpcmd, tmp, &fcpdev->fcpcmds, list ) {
+               fcpcmd_get ( fcpcmd );
+               fcpcmd_close ( fcpcmd, rc );
+               fcpcmd_put ( fcpcmd );
+       }
+
+       /* Drop reference to ULP */
+       fc_ulp_detach ( &fcpdev->user );
+}
+
+/**
+ * Check FCP device flow-control window
+ *
+ * @v fcpdev           FCP device
+ * @ret len            Length of window
+ */
+static size_t fcpdev_window ( struct fcp_device *fcpdev ) {
+       return ( fc_link_ok ( &fcpdev->user.ulp->link ) ?
+                ~( ( size_t ) 0 ) : 0 );
+}
+
+/**
+ * Describe FCP device in an ACPI table
+ *
+ * @v fcpdev           FCP device
+ * @v acpi             ACPI table
+ * @v len              Length of ACPI table
+ * @ret rc             Return status code
+ */
+static int fcpdev_acpi_describe ( struct fcp_device *fcpdev,
+                                 struct acpi_description_header *acpi,
+                                 size_t len ) {
+
+       DBGC ( fcpdev, "FCP %p cannot yet describe device in an ACPI table\n",
+              fcpdev );
+       ( void ) acpi;
+       ( void ) len;
+       return 0;
+}
+
+/**
+ * Describe FCP device using EDD
+ *
+ * @v fcpdev           FCP device
+ * @v type             EDD interface type
+ * @v path             EDD device path
+ * @ret rc             Return status code
+ */
+static int fcpdev_edd_describe ( struct fcp_device *fcpdev,
+                                struct edd_interface_type *type,
+                                union edd_device_path *path ) {
+       union {
+               struct fc_name fc;
+               uint64_t u64;
+       } wwn;
+       union {
+               struct scsi_lun scsi;
+               uint64_t u64;
+       } lun;
+
+       type->type = cpu_to_le64 ( EDD_INTF_TYPE_FIBRE );
+       memcpy ( &wwn.fc, &fcpdev->wwn, sizeof ( wwn.fc ) );
+       path->fibre.wwn = be64_to_cpu ( wwn.u64 );
+       memcpy ( &lun.scsi, &fcpdev->lun, sizeof ( lun.scsi ) );
+       path->fibre.lun = be64_to_cpu ( lun.u64 );
+       return 0;
+}
+
+/**
+ * Identify device underlying FCP device
+ *
+ * @v fcpdev           FCP device
+ * @ret device         Underlying device
+ */
+static struct device * fcpdev_identify_device ( struct fcp_device *fcpdev ) {
+
+       /* We know the underlying device only if the link is up;
+        * otherwise we don't have a port to examine.
+        */
+       if ( ! fc_link_ok ( &fcpdev->user.ulp->link ) ) {
+               DBGC ( fcpdev, "FCP %p doesn't know underlying device "
+                      "until link is up\n", fcpdev );
+               return NULL;
+       }
+
+       /* Hand off to port's transport interface */
+       assert ( fcpdev->user.ulp->peer->port != NULL );
+       return identify_device ( &fcpdev->user.ulp->peer->port->transport );
+}
+
+/** FCP device SCSI interface operations */
+static struct interface_operation fcpdev_scsi_op[] = {
+       INTF_OP ( scsi_command, struct fcp_device *, fcpdev_scsi_command ),
+       INTF_OP ( xfer_window, struct fcp_device *, fcpdev_window ),
+       INTF_OP ( intf_close, struct fcp_device *, fcpdev_close ),
+       INTF_OP ( acpi_describe, struct fcp_device *, fcpdev_acpi_describe ),
+       INTF_OP ( edd_describe, struct fcp_device *, fcpdev_edd_describe ),
+       INTF_OP ( identify_device, struct fcp_device *,
+                 fcpdev_identify_device ),
+};
+
+/** FCP device SCSI interface descriptor */
+static struct interface_descriptor fcpdev_scsi_desc =
+       INTF_DESC ( struct fcp_device, scsi, fcpdev_scsi_op );
+
+/**
+ * Examine FCP ULP link state
+ *
+ * @v user             Fibre Channel upper-layer protocol user
+ */
+static void fcpdev_examine ( struct fc_ulp_user *user ) {
+       struct fcp_device *fcpdev =
+               container_of ( user, struct fcp_device, user );
+
+       if ( fc_link_ok ( &fcpdev->user.ulp->link ) ) {
+               DBGC ( fcpdev, "FCP %p link is up\n", fcpdev );
+       } else {
+               DBGC ( fcpdev, "FCP %p link is down: %s\n",
+                      fcpdev, strerror ( fcpdev->user.ulp->link.rc ) );
+       }
+
+       /* Notify SCSI layer of window change */
+       xfer_window_changed ( &fcpdev->scsi );
+}
+
+/**
+ * Open FCP device
+ *
+ * @v parent           Parent interface
+ * @v wwn              Fibre Channel WWN
+ * @v lun              SCSI LUN
+ * @ret rc             Return status code
+ */
+static int fcpdev_open ( struct interface *parent, struct fc_name *wwn,
+                        struct scsi_lun *lun ) {
+       struct fc_ulp *ulp;
+       struct fcp_device *fcpdev;
+       int rc;
+
+       /* Get Fibre Channel ULP interface */
+       ulp = fc_ulp_get_wwn_type ( wwn, FC_TYPE_FCP );
+       if ( ! ulp ) {
+               rc = -ENOMEM;
+               goto err_ulp_get;
+       }
+
+       /* Allocate and initialise structure */
+       fcpdev = zalloc ( sizeof ( *fcpdev ) );
+       if ( ! fcpdev ) {
+               rc = -ENOMEM;
+               goto err_zalloc;
+       }
+       ref_init ( &fcpdev->refcnt, NULL );
+       intf_init ( &fcpdev->scsi, &fcpdev_scsi_desc, &fcpdev->refcnt );
+       INIT_LIST_HEAD ( &fcpdev->fcpcmds );
+       fc_ulp_user_init ( &fcpdev->user, fcpdev_examine, &fcpdev->refcnt );
+
+       DBGC ( fcpdev, "FCP %p opened for %s\n", fcpdev, fc_ntoa ( wwn ) );
+
+       /* Attach to Fibre Channel ULP */
+       fc_ulp_attach ( ulp, &fcpdev->user );
+
+       /* Preserve parameters required for boot firmware table */
+       memcpy ( &fcpdev->wwn, wwn, sizeof ( fcpdev->wwn ) );
+       memcpy ( &fcpdev->lun, lun, sizeof ( fcpdev->lun ) );
+
+       /* Attach SCSI device to parent interface */
+       if ( ( rc = scsi_open ( parent, &fcpdev->scsi, lun ) ) != 0 ) {
+               DBGC ( fcpdev, "FCP %p could not create SCSI device: %s\n",
+                      fcpdev, strerror ( rc ) );
+               goto err_scsi_open;
+       }
+
+       /* Drop temporary reference to ULP */
+       fc_ulp_put ( ulp );
+
+       /* Mortalise self and return */
+       ref_put ( &fcpdev->refcnt );
+       return 0;
+
+ err_scsi_open:
+       fcpdev_close ( fcpdev, rc );
+       ref_put ( &fcpdev->refcnt );
+ err_zalloc:
+       fc_ulp_put ( ulp );
+ err_ulp_get:
+       return rc;
+}
+
+/******************************************************************************
+ *
+ * FCP URIs
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Parse FCP URI
+ *
+ * @v uri              URI
+ * @ret wwn            Fibre Channel WWN
+ * @ret lun            SCSI LUN
+ * @ret rc             Return status code
+ *
+ * An FCP URI has the form "fcp:<wwn>:<lun>" or "fcp://<wwn>/<lun>"
+ */
+static int fcp_parse_uri ( struct uri *uri, struct fc_name *wwn,
+                          struct scsi_lun *lun ) {
+       char wwn_buf[ FC_NAME_STRLEN + 1 /* NUL */ ];
+       const char *wwn_text;
+       const char *lun_text;
+       int rc;
+
+       /* Extract WWN and LUN texts from URI */
+       if ( uri->opaque ) {
+               /* "fcp:<wwn>:<lun>" */
+               if ( snprintf ( wwn_buf, sizeof ( wwn_buf ), "%s",
+                               uri->opaque ) < ( FC_NAME_STRLEN + 1 /* : */ ) )
+                       return -EINVAL;
+               if ( uri->opaque[FC_NAME_STRLEN] != ':' )
+                       return -EINVAL;
+               wwn_text = wwn_buf;
+               lun_text = &uri->opaque[FC_NAME_STRLEN + 1];
+       } else {
+               /* If host exists, path must also exist */
+               if ( ! ( uri->host && uri->path ) )
+                       return -EINVAL;
+               if ( uri->path[0] != '/' )
+                       return -EINVAL;
+               wwn_text = uri->host;
+               lun_text = ( uri->path + 1 );
+       }
+
+       /* Parse WWN */
+       if ( ( rc = fc_aton ( wwn_text, wwn ) ) != 0 )
+               return rc;
+
+       /* Parse LUN */
+       if ( ( rc = scsi_parse_lun ( lun_text, lun ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Open FCP URI
+ *
+ * @v parent           Parent interface
+ * @v uri              URI
+ * @ret rc             Return status code
+ */
+static int fcp_open ( struct interface *parent, struct uri *uri ) {
+       struct fc_name wwn;
+       struct scsi_lun lun;
+       int rc;
+
+       /* Parse URI */
+       if ( ( rc = fcp_parse_uri ( uri, &wwn, &lun ) ) != 0 )
+               return rc;
+
+       /* Open FCP device */
+       if ( ( rc = fcpdev_open ( parent, &wwn, &lun ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/** FCP URI opener */
+struct uri_opener fcp_uri_opener __uri_opener = {
+       .scheme = "fcp",
+       .open = fcp_open,
+};