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