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