/* * 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., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #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, 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 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 ( 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::" 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, };