diff options
Diffstat (limited to 'src/drivers/block')
-rw-r--r-- | src/drivers/block/srp.c | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/src/drivers/block/srp.c b/src/drivers/block/srp.c new file mode 100644 index 000000000..f50f194ba --- /dev/null +++ b/src/drivers/block/srp.c @@ -0,0 +1,530 @@ +/* + * 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 <gpxe/scsi.h> +#include <gpxe/xfer.h> +#include <gpxe/features.h> +#include <gpxe/ib_srp.h> +#include <gpxe/srp.h> + +/** + * @file + * + * SCSI RDMA Protocol + * + */ + +FEATURE ( FEATURE_PROTOCOL, "SRP", DHCP_EB_FEATURE_SRP, 1 ); + +/** Tag to be used for next SRP IU */ +static unsigned int srp_tag = 0; + +static void srp_login ( struct srp_device *srp ); +static void srp_cmd ( struct srp_device *srp ); + +/** + * Mark SRP SCSI command as complete + * + * @v srp SRP device + * @v rc Status code + */ +static void srp_scsi_done ( struct srp_device *srp, int rc ) { + if ( srp->command ) + srp->command->rc = rc; + srp->command = NULL; +} + +/** + * Handle SRP session failure + * + * @v srp SRP device + * @v rc Reason for failure + */ +static void srp_fail ( struct srp_device *srp, int rc ) { + + /* Close underlying socket */ + xfer_close ( &srp->socket, rc ); + + /* Clear session state */ + srp->state = 0; + + /* Increment retry count */ + srp->retry_count++; + + /* If we have reached the retry limit, permanently abort the + * session. + */ + if ( srp->retry_count >= SRP_MAX_RETRIES ) { + srp->instant_rc = rc; + srp_scsi_done ( srp, rc ); + return; + } + + /* Otherwise, try to reopen the connection */ + srp_login ( srp ); +} + +/** + * Initiate SRP login + * + * @v srp SRP device + */ +static void srp_login ( struct srp_device *srp ) { + struct io_buffer *iobuf; + struct srp_login_req *login_req; + int rc; + + assert ( ! ( srp->state & SRP_STATE_SOCKET_OPEN ) ); + + /* Open underlying socket */ + if ( ( rc = srp->transport->connect ( srp ) ) != 0 ) { + DBGC ( srp, "SRP %p could not open socket: %s\n", + srp, strerror ( rc ) ); + goto err; + } + srp->state |= SRP_STATE_SOCKET_OPEN; + + /* Allocate I/O buffer */ + iobuf = xfer_alloc_iob ( &srp->socket, sizeof ( *login_req ) ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err; + } + + /* 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[1] = htonl ( ++srp_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->port_ids, &srp->port_ids, + sizeof ( login_req->port_ids ) ); + + DBGC2 ( srp, "SRP %p TX login request tag %08x%08x\n", + srp, ntohl ( login_req->tag.dwords[0] ), + ntohl ( login_req->tag.dwords[1] ) ); + DBGC2_HDA ( srp, 0, iobuf->data, iob_len ( iobuf ) ); + + /* Send login request IU */ + if ( ( rc = xfer_deliver_iob ( &srp->socket, iobuf ) ) != 0 ) { + DBGC ( srp, "SRP %p could not send login request: %s\n", + srp, strerror ( rc ) ); + goto err; + } + + return; + + err: + srp_fail ( srp, rc ); +} + +/** + * Handle SRP login response + * + * @v srp SRP device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int srp_login_rsp ( struct srp_device *srp, struct io_buffer *iobuf ) { + struct srp_login_rsp *login_rsp = iobuf->data; + int rc; + + DBGC2 ( srp, "SRP %p RX login response tag %08x%08x\n", + srp, ntohl ( login_rsp->tag.dwords[0] ), + ntohl ( login_rsp->tag.dwords[1] ) ); + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *login_rsp ) ) { + DBGC ( srp, "SRP %p RX login response too short (%zd bytes)\n", + srp, iob_len ( iobuf ) ); + rc = -EINVAL; + goto out; + } + + DBGC ( srp, "SRP %p logged in\n", srp ); + + /* Mark as logged in */ + srp->state |= SRP_STATE_LOGGED_IN; + + /* Reset error counter */ + srp->retry_count = 0; + + /* Issue pending command */ + srp_cmd ( srp ); + + rc = 0; + out: + free_iob ( iobuf ); + return rc; +} + +/** + * Handle SRP login rejection + * + * @v srp SRP device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int srp_login_rej ( struct srp_device *srp, struct io_buffer *iobuf ) { + struct srp_login_rej *login_rej = iobuf->data; + int rc; + + DBGC2 ( srp, "SRP %p RX login rejection tag %08x%08x\n", + srp, ntohl ( login_rej->tag.dwords[0] ), + ntohl ( login_rej->tag.dwords[1] ) ); + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *login_rej ) ) { + DBGC ( srp, "SRP %p RX login rejection too short (%zd " + "bytes)\n", srp, iob_len ( iobuf ) ); + rc = -EINVAL; + goto out; + } + + /* Login rejection always indicates an error */ + DBGC ( srp, "SRP %p login rejected (reason %08x)\n", + srp, ntohl ( login_rej->reason ) ); + rc = -EPERM; + + out: + free_iob ( iobuf ); + return rc; +} + +/** + * Transmit SRP SCSI command + * + * @v srp SRP device + */ +static void srp_cmd ( struct srp_device *srp ) { + struct io_buffer *iobuf; + struct srp_cmd *cmd; + struct srp_memory_descriptor *data_out; + struct srp_memory_descriptor *data_in; + int rc; + + assert ( srp->state & SRP_STATE_LOGGED_IN ); + + /* Allocate I/O buffer */ + iobuf = xfer_alloc_iob ( &srp->socket, SRP_MAX_I_T_IU_LEN ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err; + } + + /* Construct base portion */ + cmd = iob_put ( iobuf, sizeof ( *cmd ) ); + memset ( cmd, 0, sizeof ( *cmd ) ); + cmd->type = SRP_CMD; + cmd->tag.dwords[1] = htonl ( ++srp_tag ); + cmd->lun = srp->lun; + memcpy ( &cmd->cdb, &srp->command->cdb, sizeof ( cmd->cdb ) ); + + /* Construct data-out descriptor, if present */ + if ( srp->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 ( srp->command->data_out, 0 ) ); + data_out->handle = ntohl ( srp->memory_handle ); + data_out->len = ntohl ( srp->command->data_out_len ); + } + + /* Construct data-in descriptor, if present */ + if ( srp->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 ( srp->command->data_in, 0 ) ); + data_in->handle = ntohl ( srp->memory_handle ); + data_in->len = ntohl ( srp->command->data_in_len ); + } + + DBGC2 ( srp, "SRP %p TX SCSI command tag %08x%08x\n", srp, + ntohl ( cmd->tag.dwords[0] ), ntohl ( cmd->tag.dwords[1] ) ); + DBGC2_HDA ( srp, 0, iobuf->data, iob_len ( iobuf ) ); + + /* Send IU */ + if ( ( rc = xfer_deliver_iob ( &srp->socket, iobuf ) ) != 0 ) { + DBGC ( srp, "SRP %p could not send command: %s\n", + srp, strerror ( rc ) ); + goto err; + } + + return; + + err: + srp_fail ( srp, rc ); +} + +/** + * Handle SRP SCSI response + * + * @v srp SRP device + * @v iobuf I/O buffer + * @ret rc Returns status code + */ +static int srp_rsp ( struct srp_device *srp, struct io_buffer *iobuf ) { + struct srp_rsp *rsp = iobuf->data; + int rc; + + DBGC2 ( srp, "SRP %p RX SCSI response tag %08x%08x\n", srp, + ntohl ( rsp->tag.dwords[0] ), ntohl ( rsp->tag.dwords[1] ) ); + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *rsp ) ) { + DBGC ( srp, "SRP %p RX SCSI response too short (%zd bytes)\n", + srp, iob_len ( iobuf ) ); + rc = -EINVAL; + goto out; + } + + /* Report SCSI errors */ + if ( rsp->status != 0 ) { + DBGC ( srp, "SRP %p response status %02x\n", + srp, rsp->status ); + if ( srp_rsp_sense_data ( rsp ) ) { + DBGC ( srp, "SRP %p sense data:\n", srp ); + DBGC_HDA ( srp, 0, srp_rsp_sense_data ( rsp ), + srp_rsp_sense_data_len ( rsp ) ); + } + } + if ( rsp->valid & ( SRP_RSP_VALID_DOUNDER | SRP_RSP_VALID_DOOVER ) ) { + DBGC ( srp, "SRP %p response data-out %srun by %#x bytes\n", + srp, ( ( rsp->valid & SRP_RSP_VALID_DOUNDER ) + ? "under" : "over" ), + ntohl ( rsp->data_out_residual_count ) ); + } + if ( rsp->valid & ( SRP_RSP_VALID_DIUNDER | SRP_RSP_VALID_DIOVER ) ) { + DBGC ( srp, "SRP %p response data-in %srun by %#x bytes\n", + srp, ( ( rsp->valid & SRP_RSP_VALID_DIUNDER ) + ? "under" : "over" ), + ntohl ( rsp->data_in_residual_count ) ); + } + srp->command->status = rsp->status; + + /* Mark SCSI command as complete */ + srp_scsi_done ( srp, 0 ); + + rc = 0; + out: + free_iob ( iobuf ); + return rc; +} + +/** + * Handle SRP unrecognised response + * + * @v srp SRP device + * @v iobuf I/O buffer + * @ret rc Returns status code + */ +static int srp_unrecognised ( struct srp_device *srp, + struct io_buffer *iobuf ) { + struct srp_common *common = iobuf->data; + + DBGC ( srp, "SRP %p RX unrecognised IU tag %08x%08x type %02x\n", + srp, ntohl ( common->tag.dwords[0] ), + ntohl ( common->tag.dwords[1] ), common->type ); + + free_iob ( iobuf ); + return -ENOTSUP; +} + +/** + * Receive data from underlying socket + * + * @v xfer Data transfer interface + * @v iobuf Datagram I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int srp_xfer_deliver_iob ( struct xfer_interface *xfer, + struct io_buffer *iobuf, + struct xfer_metadata *meta __unused ) { + struct srp_device *srp = + container_of ( xfer, struct srp_device, socket ); + struct srp_common *common = iobuf->data; + int ( * type ) ( struct srp_device *srp, struct io_buffer *iobuf ); + int rc; + + /* 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 ( srp, iobuf ) ) != 0 ) + goto err; + + return 0; + + err: + srp_fail ( srp, rc ); + return rc; +} + +/** + * Underlying socket closed + * + * @v xfer Data transfer interface + * @v rc Reason for close + */ +static void srp_xfer_close ( struct xfer_interface *xfer, int rc ) { + struct srp_device *srp = + container_of ( xfer, struct srp_device, socket ); + + DBGC ( srp, "SRP %p socket closed: %s\n", srp, strerror ( rc ) ); + + srp_fail ( srp, rc ); +} + +/** SRP data transfer interface operations */ +static struct xfer_interface_operations srp_xfer_operations = { + .close = srp_xfer_close, + .vredirect = ignore_xfer_vredirect, + .window = unlimited_xfer_window, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = srp_xfer_deliver_iob, + .deliver_raw = xfer_deliver_as_iob, +}; + +/** + * Issue SCSI command via SRP + * + * @v scsi SCSI device + * @v command SCSI command + * @ret rc Return status code + */ +static int srp_command ( struct scsi_device *scsi, + struct scsi_command *command ) { + struct srp_device *srp = + container_of ( scsi->backend, struct srp_device, refcnt ); + + /* Return instant failure, if we have already aborted the session */ + if ( srp->instant_rc ) + return srp->instant_rc; + + /* Store SCSI command */ + if ( srp->command ) { + DBGC ( srp, "SRP %p cannot handle concurrent SCSI commands\n", + srp ); + return -EBUSY; + } + srp->command = command; + + /* Log in or issue command as appropriate */ + if ( ! ( srp->state & SRP_STATE_SOCKET_OPEN ) ) { + srp_login ( srp ); + } else if ( srp->state & SRP_STATE_LOGGED_IN ) { + srp_cmd ( srp ); + } else { + /* Still waiting for login; do nothing */ + } + + return 0; +} + +/** + * Attach SRP device + * + * @v scsi SCSI device + * @v root_path Root path + */ +int srp_attach ( struct scsi_device *scsi, const char *root_path ) { + struct srp_transport_type *transport; + struct srp_device *srp; + int rc; + + /* Hard-code an IB SRP back-end for now */ + transport = &ib_srp_transport; + + /* Allocate and initialise structure */ + srp = zalloc ( sizeof ( *srp ) + transport->priv_len ); + if ( ! srp ) { + rc = -ENOMEM; + goto err_alloc; + } + xfer_init ( &srp->socket, &srp_xfer_operations, &srp->refcnt ); + srp->transport = transport; + DBGC ( srp, "SRP %p using %s\n", srp, root_path ); + + /* Parse root path */ + if ( ( rc = transport->parse_root_path ( srp, root_path ) ) != 0 ) { + DBGC ( srp, "SRP %p could not parse root path: %s\n", + srp, strerror ( rc ) ); + goto err_parse_root_path; + } + + /* Attach parent interface, mortalise self, and return */ + scsi->backend = ref_get ( &srp->refcnt ); + scsi->command = srp_command; + ref_put ( &srp->refcnt ); + return 0; + + err_parse_root_path: + ref_put ( &srp->refcnt ); + err_alloc: + return rc; +} + +/** + * Detach SRP device + * + * @v scsi SCSI device + */ +void srp_detach ( struct scsi_device *scsi ) { + struct srp_device *srp = + container_of ( scsi->backend, struct srp_device, refcnt ); + + /* Close socket */ + xfer_nullify ( &srp->socket ); + xfer_close ( &srp->socket, 0 ); + scsi->command = scsi_detached_command; + ref_put ( scsi->backend ); + scsi->backend = NULL; +} |