diff --git a/src/config/config_fc.c b/src/config/config_fc.c index 26d9bf42..41464699 100644 --- a/src/config/config_fc.c +++ b/src/config/config_fc.c @@ -22,3 +22,10 @@ FILE_LICENCE ( GPL2_OR_LATER ); #ifdef FCMGMT_CMD REQUIRE_OBJECT ( fcmgmt_cmd ); #endif + +/* + * Drag in Fibre Channel-specific protocols + */ +#ifdef SANBOOT_PROTO_FCP +REQUIRE_OBJECT ( fcp ); +#endif diff --git a/src/config/defaults/pcbios.h b/src/config/defaults/pcbios.h index d2ffc3d2..165ee14c 100644 --- a/src/config/defaults/pcbios.h +++ b/src/config/defaults/pcbios.h @@ -33,5 +33,6 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define SANBOOT_PROTO_ISCSI /* iSCSI protocol */ #define SANBOOT_PROTO_AOE /* AoE protocol */ #define SANBOOT_PROTO_IB_SRP /* Infiniband SCSI RDMA protocol */ +#define SANBOOT_PROTO_FCP /* Fibre Channel protocol */ #endif /* CONFIG_DEFAULTS_PCBIOS_H */ diff --git a/src/config/general.h b/src/config/general.h index ee7f9034..661e834c 100644 --- a/src/config/general.h +++ b/src/config/general.h @@ -68,6 +68,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); //#undef SANBOOT_PROTO_ISCSI /* iSCSI protocol */ //#undef SANBOOT_PROTO_AOE /* AoE protocol */ //#undef SANBOOT_PROTO_IB_SRP /* Infiniband SCSI RDMA protocol */ +//#undef SANBOOT_PROTO_FCP /* Fibre Channel protocol */ /* * 802.11 cryptosystems and handshaking protocols diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index d530d902..2ae32412 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -186,6 +186,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define ERRFILE_eth_slow ( ERRFILE_NET | 0x002a0000 ) #define ERRFILE_fc ( ERRFILE_NET | 0x002b0000 ) #define ERRFILE_fcels ( ERRFILE_NET | 0x002c0000 ) +#define ERRFILE_fcp ( ERRFILE_NET | 0x002d0000 ) #define ERRFILE_image ( ERRFILE_IMAGE | 0x00000000 ) #define ERRFILE_elf ( ERRFILE_IMAGE | 0x00010000 ) diff --git a/src/include/ipxe/fcp.h b/src/include/ipxe/fcp.h new file mode 100644 index 00000000..f6922bc7 --- /dev/null +++ b/src/include/ipxe/fcp.h @@ -0,0 +1,166 @@ +#ifndef _IPXE_FCP_H +#define _IPXE_FCP_H + +/** + * @file + * + * Fibre Channel Protocol + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include +#include +#include + +/** An FCP command IU */ +struct fcp_cmnd { + /** SCSI LUN */ + struct scsi_lun lun; + /** Command reference number */ + uint8_t ref; + /** Priority and task attributes */ + uint8_t priority; + /** Task management flags */ + uint8_t flags; + /** Direction */ + uint8_t dirn; + /** SCSI CDB */ + union scsi_cdb cdb; + /** Data length */ + uint32_t len; +} __attribute__ (( packed )); + +/** Command includes data-out */ +#define FCP_CMND_WRDATA 0x01 + +/** Command includes data-in */ +#define FCP_CMND_RDDATA 0x02 + +/** FCP tag magic marker */ +#define FCP_TAG_MAGIC 0x18ae0000 + +/** An FCP transfer ready IU */ +struct fcp_xfer_rdy { + /** Relative offset of data */ + uint32_t offset; + /** Burst length */ + uint32_t len; + /** Reserved */ + uint32_t reserved; +} __attribute__ (( packed )); + +/** An FCP response IU */ +struct fcp_rsp { + /** Reserved */ + uint8_t reserved[8]; + /** Retry delay timer */ + uint16_t retry_delay; + /** Flags */ + uint8_t flags; + /** SCSI status code */ + uint8_t status; + /** Residual data count */ + uint32_t residual; + /** Sense data length */ + uint32_t sense_len; + /** Response data length */ + uint32_t response_len; +} __attribute__ (( packed )); + +/** Response length field is valid */ +#define FCP_RSP_RESPONSE_LEN_VALID 0x01 + +/** Sense length field is valid */ +#define FCP_RSP_SENSE_LEN_VALID 0x02 + +/** Residual represents overrun */ +#define FCP_RSP_RESIDUAL_OVERRUN 0x04 + +/** Residual represents underrun */ +#define FCP_RSP_RESIDUAL_UNDERRUN 0x08 + +/** + * Get response data portion of FCP response + * + * @v rsp FCP response + * @ret response_data Response data, or NULL if not present + */ +static inline void * fcp_rsp_response_data ( struct fcp_rsp *rsp ) { + return ( ( rsp->flags & FCP_RSP_RESPONSE_LEN_VALID ) ? + ( ( ( void * ) rsp ) + sizeof ( *rsp ) ) : NULL ); +} + +/** + * Get length of response data portion of FCP response + * + * @v rsp FCP response + * @ret response_data_len Response data length + */ +static inline size_t fcp_rsp_response_data_len ( struct fcp_rsp *rsp ) { + return ( ( rsp->flags & FCP_RSP_RESPONSE_LEN_VALID ) ? + ntohl ( rsp->response_len ) : 0 ); +} + +/** + * Get sense data portion of FCP response + * + * @v rsp FCP response + * @ret sense_data Sense data, or NULL if not present + */ +static inline void * fcp_rsp_sense_data ( struct fcp_rsp *rsp ) { + return ( ( rsp->flags & FCP_RSP_SENSE_LEN_VALID ) ? + ( ( ( void * ) rsp ) + sizeof ( *rsp ) + + fcp_rsp_response_data_len ( rsp ) ) : NULL ); +} + +/** + * Get length of sense data portion of FCP response + * + * @v rsp FCP response + * @ret sense_data_len Sense data length + */ +static inline size_t fcp_rsp_sense_data_len ( struct fcp_rsp *rsp ) { + return ( ( rsp->flags & FCP_RSP_SENSE_LEN_VALID ) ? + ntohl ( rsp->sense_len ) : 0 ); +} + +/** An FCP PRLI service parameter page */ +struct fcp_prli_service_parameters { + /** Flags */ + uint32_t flags; +} __attribute__ (( packed )); + +/** Write FCP_XFER_RDY disabled */ +#define FCP_PRLI_NO_WRITE_RDY 0x0001 + +/** Read FCP_XFER_RDY disabled */ +#define FCP_PRLI_NO_READ_RDY 0x0002 + +/** Has target functionality */ +#define FCP_PRLI_TARGET 0x0010 + +/** Has initiator functionality */ +#define FCP_PRLI_INITIATOR 0x0020 + +/** Data overlay allowed */ +#define FCP_PRLI_OVERLAY 0x0040 + +/** Confirm completion allowed */ +#define FCP_PRLI_CONF 0x0080 + +/** Retransmission supported */ +#define FCP_PRLI_RETRY 0x0100 + +/** Task retry identification */ +#define FCP_PRLI_TASK_RETRY 0x0200 + +/** REC ELS supported */ +#define FCP_PRLI_REC 0x0400 + +/** Enhanced discovery supported */ +#define FCP_PRLI_ENH_DISC 0x0800 + +#endif /* _IPXE_FCP_H */ diff --git a/src/net/fcp.c b/src/net/fcp.c new file mode 100644 index 00000000..42f6528e --- /dev/null +++ b/src/net/fcp.c @@ -0,0 +1,1005 @@ +/* + * Copyright (C) 2010 Michael Brown . + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @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 ( ¶m, 0, sizeof ( param ) ); + param.flags = htonl ( FCP_PRLI_NO_READ_RDY | FCP_PRLI_INITIATOR ); + + return fc_els_prli_tx ( els, &fcp_prli_descriptor, ¶m ); +} + +/** + * 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, const 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_request = fcp_prli_tx, + .tx_response = fcp_prli_tx, + .rx_request = fcp_prli_rx, + .rx_response = 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 */ + struct fc_ulp *ulp; + /** SCSI command issuing interface */ + struct interface scsi; + /** List of active commands */ + struct list_head fcpcmds; +}; + +/** 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; + /** Command reference */ + uint8_t ref; +}; + +/** + * 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 ref %02x closed: %s\n", + fcpdev, fcpcmd->ref, 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 ref %02x cannot handle bidirectional " + "command\n", fcpdev, fcpcmd->ref ); + return -ENOTSUP; + } + + /* Allocate I/O buffer */ + iobuf = xfer_alloc_iob ( &fcpcmd->xchg, sizeof ( *cmnd ) ); + if ( ! iobuf ) { + DBGC ( fcpdev, "FCP %p ref %02x cannot allocate command IU\n", + fcpdev, fcpcmd->ref ); + 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 ) ); + cmnd->ref = fcpcmd->ref; + 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 ref %02x CMND " SCSI_CDB_FORMAT " %04x\n", + fcpdev, fcpcmd->ref, 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 ref %02x cannot deliver command IU: " + "%s\n", fcpdev, fcpcmd->ref, 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 ref %02x read data missing offset\n", + fcpdev, fcpcmd->ref ); + rc = -ERANGE_READ_DATA_ORDERING; + goto done; + } + if ( offset != fcpcmd->offset ) { + DBGC ( fcpdev, "FCP %p ref %02x read data out of order " + "(expected %zd, received %zd)\n", + fcpdev, fcpcmd->ref, fcpcmd->offset, offset ); + rc = -ERANGE_READ_DATA_ORDERING; + goto done; + } + if ( ( offset + len ) > command->data_in_len ) { + DBGC ( fcpdev, "FCP %p ref %02x read data overrun (max %zd, " + "received %zd)\n", fcpdev, fcpcmd->ref, + command->data_in_len, ( offset + len ) ); + rc = -ERANGE_READ_DATA_OVERRUN; + goto done; + } + DBGC2 ( fcpdev, "FCP %p ref %02x RDDATA [%08zx,%08zx)\n", + fcpdev, fcpcmd->ref, 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 ref %02x write data stuck\n", + fcpdev, fcpcmd->ref ); + return -ERANGE_WRITE_DATA_STUCK; + } + if ( ( fcpcmd->offset + len ) > command->data_out_len ) { + DBGC ( fcpdev, "FCP %p ref %02x write data overrun (max %zd, " + "requested %zd)\n", fcpdev, fcpcmd->ref, + 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 ref %02x cannot allocate write data IU " + "for %zd bytes\n", fcpdev, fcpcmd->ref, 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 ref %02x WRDATA [%08zx,%04zx)\n", + fcpdev, fcpcmd->ref, 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 ref %02x cannot deliver write data IU: " + "%s\n", fcpdev, fcpcmd->ref, 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 ref %02x received invalid transfer " + "ready IU:\n", fcpdev, fcpcmd->ref ); + DBGC_HDA ( fcpdev, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EINVAL; + goto done; + } + if ( ntohl ( xfer_rdy->offset ) != fcpcmd->offset ) { + /* We do not advertise out-of-order delivery */ + DBGC ( fcpdev, "FCP %p ref %02x cannot support out-of-order " + "delivery (expected %zd, requested %d)\n", + fcpdev, fcpcmd->ref, fcpcmd->offset, + ntohl ( xfer_rdy->offset ) ); + rc = -EINVAL; + goto done; + } + DBGC2 ( fcpdev, "FCP %p ref %02x XFER_RDY [%08x,%08x)\n", + fcpdev, fcpcmd->ref, 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_sense *sense; + 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 ref %02x received invalid response " + "IU:\n", fcpdev, fcpcmd->ref ); + DBGC_HDA ( fcpdev, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EINVAL; + goto done; + } + DBGC2 ( fcpdev, "FCP %p ref %02x RSP stat %02x resid %08x flags %02x" + "%s%s%s%s\n", fcpdev, fcpcmd->ref, 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 ref %02x response data:\n", + fcpdev, fcpcmd->ref ); + 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 ref %02x sense data:\n", + fcpdev, fcpcmd->ref ); + 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 ref %02x data underrun (expected %zd, " + "got %zd)\n", fcpdev, fcpcmd->ref, + ( 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; + } + if ( ( sense = fcp_rsp_sense_data ( rsp ) ) != NULL ) + memcpy ( &response.sense, sense, sizeof ( 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 ref %02x received unknown IU:\n", + fcpdev, fcpcmd->ref ); + DBGC_HDA ( fcpdev, 0, iobuf->data, iob_len ( iobuf ) ); + free_iob ( iobuf ); + return -EINVAL; +} + +/** + * Transmit FCP frame + * + * @v process FCP command process + */ +static void fcpcmd_step ( struct process *process ) { + struct fcp_command *fcpcmd = + container_of ( process, struct fcp_command, process ); + 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 ); + +/** + * 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->ulp->param; + static uint8_t ref = 0; + struct fcp_command *fcpcmd; + int rc; + + /* Check link */ + if ( ( rc = fcpdev->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->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_step, &fcpcmd->refcnt ); + fcpcmd->fcpdev = fcpdev_get ( fcpdev ); + list_add ( &fcpcmd->list, &fcpdev->fcpcmds ); + memcpy ( &fcpcmd->command, command, sizeof ( fcpcmd->command ) ); + fcpcmd->ref = ref++; /* Not used for demultiplexing, only for debug */ + + /* Create new exchange */ + if ( ( rc = fc_xchg_originate ( &fcpcmd->xchg, fcpdev->ulp->peer->port, + &fcpdev->ulp->peer->port_id, + FC_TYPE_FCP ) ) != 0 ) { + DBGC ( fcpdev, "FCP %p ref %02x could not create exchange: " + "%s\n", fcpdev, fcpcmd->ref, strerror ( rc ) ); + goto err_xchg_originate; + } + + /* 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->ref ); + + err_xchg_originate: + fcpcmd_close ( fcpcmd, rc ); + 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 */ + if ( fcpdev->ulp ) { + fc_ulp_decrement ( fcpdev->ulp ); + fc_ulp_put ( fcpdev->ulp ); + fcpdev->ulp = NULL; + } +} + +/** + * 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->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_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; +} + +/** 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_describe ), +}; + +/** FCP device SCSI interface descriptor */ +static struct interface_descriptor fcpdev_scsi_desc = + INTF_DESC ( struct fcp_device, scsi, fcpdev_scsi_op ); + +/** + * 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 ); + fcpdev->ulp = fc_ulp_get ( ulp ); + fc_ulp_increment ( fcpdev->ulp ); + + DBGC ( fcpdev, "FCP %p opened for %s\n", fcpdev, fc_ntoa ( wwn ) ); + + /* 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 ); + 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::" or "fcp:///" + */ +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::" */ + 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, +};