From e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb Mon Sep 17 00:00:00 2001 From: Yang Zhang Date: Fri, 28 Aug 2015 09:58:54 +0800 Subject: Add qemu 2.4.0 Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5 Signed-off-by: Yang Zhang --- qemu/roms/ipxe/src/drivers/block/srp.c | 829 +++++++++++++++++++++++++++++++++ 1 file changed, 829 insertions(+) create mode 100644 qemu/roms/ipxe/src/drivers/block/srp.c (limited to 'qemu/roms/ipxe/src/drivers/block/srp.c') diff --git a/qemu/roms/ipxe/src/drivers/block/srp.c b/qemu/roms/ipxe/src/drivers/block/srp.c new file mode 100644 index 000000000..7edf69ace --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/block/srp.c @@ -0,0 +1,829 @@ +/* + * 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; +} -- cgit