762 lines
22 KiB
C
762 lines
22 KiB
C
/*
|
|
* 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 );
|
|
|
|
#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 = 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 );
|
|
}
|
|
|
|
/** 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;
|
|
}
|