08ac74b708
Signed-off-by: Michael Brown <mcb30@ipxe.org>
1339 lines
33 KiB
C
1339 lines
33 KiB
C
/*
|
|
* Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
FILE_LICENCE ( GPL2_OR_LATER );
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <byteswap.h>
|
|
#include <ipxe/interface.h>
|
|
#include <ipxe/xfer.h>
|
|
#include <ipxe/iobuf.h>
|
|
#include <ipxe/process.h>
|
|
#include <ipxe/fc.h>
|
|
#include <ipxe/fcels.h>
|
|
|
|
/** @file
|
|
*
|
|
* Fibre Channel Extended Link Services
|
|
*
|
|
*/
|
|
|
|
/** Fibre Channel ELS transaction debug message format */
|
|
#define FCELS_FMT "FCELS %s %s %s %s"
|
|
|
|
/** Fibre Channel ELS transaction debug message arguments */
|
|
#define FCELS_ARGS( els ) \
|
|
(els)->port->name, \
|
|
( (els)->handler ? (els)->handler->name : "unknown ELS" ), \
|
|
( fc_els_is_request ( els ) ? "to" : "from" ), \
|
|
fc_id_ntoa ( &(els)->peer_port_id )
|
|
|
|
struct fc_els_handler fc_els_unknown_handler __fc_els_handler;
|
|
|
|
/**
|
|
* Free Fibre Channel ELS transaction
|
|
*
|
|
* @v refcnt Reference count
|
|
*/
|
|
static void fc_els_free ( struct refcnt *refcnt ) {
|
|
struct fc_els *els = container_of ( refcnt, struct fc_els, refcnt );
|
|
|
|
assert ( ! process_running ( &els->process ) );
|
|
fc_port_put ( els->port );
|
|
free ( els );
|
|
}
|
|
|
|
/**
|
|
* Close Fibre Channel ELS transaction
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v rc Reason for close
|
|
*/
|
|
static void fc_els_close ( struct fc_els *els, int rc ) {
|
|
|
|
if ( rc != 0 ) {
|
|
DBGC ( els, FCELS_FMT " complete (%s)\n",
|
|
FCELS_ARGS ( els ), strerror ( rc ) );
|
|
}
|
|
|
|
/* Stop process */
|
|
process_del ( &els->process );
|
|
|
|
/* Shut down interfaces */
|
|
intf_shutdown ( &els->xchg, rc );
|
|
intf_shutdown ( &els->job, rc );
|
|
}
|
|
|
|
/**
|
|
* Detect Fibre Channel ELS frame handler
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v command ELS command code
|
|
* @ret handler ELS handler, or NULL
|
|
*/
|
|
static struct fc_els_handler * fc_els_detect ( struct fc_els *els,
|
|
const void *data,
|
|
size_t len ) {
|
|
const struct fc_els_frame_common *frame = data;
|
|
struct fc_els_handler *handler;
|
|
int rc;
|
|
|
|
/* Sanity check */
|
|
if ( len < sizeof ( *frame ) )
|
|
return NULL;
|
|
|
|
/* Try each handler in turn */
|
|
for_each_table_entry ( handler, FC_ELS_HANDLERS ) {
|
|
if ( ( rc = handler->detect ( els, data, len ) ) == 0 )
|
|
return handler;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Transmit Fibre Channel ELS frame
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data Data to transmit
|
|
* @v len Length of data
|
|
* @ret rc Return status code
|
|
*/
|
|
int fc_els_tx ( struct fc_els *els, const void *data, size_t len ) {
|
|
struct xfer_metadata meta;
|
|
struct sockaddr_fc dest;
|
|
int rc;
|
|
|
|
DBGC2 ( els, FCELS_FMT " transmitting:\n", FCELS_ARGS ( els ) );
|
|
DBGC2_HDA ( els, 0, data, len );
|
|
|
|
/* Construct metadata */
|
|
memset ( &meta, 0, sizeof ( meta ) );
|
|
meta.flags = ( fc_els_is_request ( els ) ?
|
|
XFER_FL_OVER : ( XFER_FL_RESPONSE | XFER_FL_OUT ) );
|
|
meta.dest = fc_fill_sockaddr ( &dest, &els->peer_port_id );
|
|
|
|
/* Transmit frame */
|
|
if ( ( rc = xfer_deliver_raw_meta ( &els->xchg, data, len,
|
|
&meta ) ) != 0 ) {
|
|
DBGC ( els, FCELS_FMT " could not deliver frame: %s\n",
|
|
FCELS_ARGS ( els ), strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Receive Fibre Channel ELS frame
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v iobuf I/O buffer
|
|
* @v meta Data transfer metadata
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_rx ( struct fc_els *els,
|
|
struct io_buffer *iobuf,
|
|
struct xfer_metadata *meta ) {
|
|
struct fc_els_frame_common *frame = iobuf->data;
|
|
struct sockaddr_fc *src = ( ( struct sockaddr_fc * ) meta->src );
|
|
struct sockaddr_fc *dest = ( ( struct sockaddr_fc * ) meta->dest );
|
|
size_t len = iob_len ( iobuf );
|
|
int rc;
|
|
|
|
/* Sanity check */
|
|
if ( len < sizeof ( *frame ) ) {
|
|
DBGC ( els, FCELS_FMT " received underlength frame:\n",
|
|
FCELS_ARGS ( els ) );
|
|
DBGC_HDA ( els, 0, frame, len );
|
|
rc = -EINVAL;
|
|
goto done;
|
|
}
|
|
if ( ! src ) {
|
|
DBGC ( els, FCELS_FMT " received frame missing source "
|
|
"address:\n", FCELS_ARGS ( els ) );
|
|
rc = -EINVAL;
|
|
goto done;
|
|
}
|
|
if ( ! dest ) {
|
|
DBGC ( els, FCELS_FMT " received frame missing destination "
|
|
"address:\n", FCELS_ARGS ( els ) );
|
|
rc = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
/* Check for rejection responses */
|
|
if ( fc_els_is_request ( els ) &&
|
|
( frame->command != FC_ELS_LS_ACC ) ) {
|
|
DBGC ( els, FCELS_FMT " rejected:\n", FCELS_ARGS ( els ) );
|
|
DBGC_HDA ( els, 0, frame, len );
|
|
rc = -EACCES;
|
|
goto done;
|
|
}
|
|
|
|
/* Update port IDs */
|
|
memcpy ( &els->port_id, &dest->sfc_port_id, sizeof ( els->port_id ) );
|
|
memcpy ( &els->peer_port_id, &src->sfc_port_id,
|
|
sizeof ( els->peer_port_id ) );
|
|
|
|
/* Determine handler, if necessary */
|
|
if ( ! els->handler )
|
|
els->handler = fc_els_detect ( els, frame, len );
|
|
if ( ! els->handler )
|
|
els->handler = &fc_els_unknown_handler;
|
|
|
|
DBGC2 ( els, FCELS_FMT " received:\n", FCELS_ARGS ( els ) );
|
|
DBGC2_HDA ( els, 0, frame, len );
|
|
|
|
/* Handle received frame */
|
|
if ( ( rc = els->handler->rx ( els, frame, len ) ) != 0 ) {
|
|
DBGC ( els, FCELS_FMT " could not handle received frame: "
|
|
"%s\n", FCELS_ARGS ( els ), strerror ( rc ) );
|
|
DBGC_HDA ( els, 0, frame, len );
|
|
goto done;
|
|
}
|
|
|
|
done:
|
|
/* Free I/O buffer */
|
|
free_iob ( iobuf );
|
|
|
|
/* Close transaction */
|
|
fc_els_close ( els, rc );
|
|
|
|
return rc;
|
|
}
|
|
|
|
/** Fibre Channel ELS exchange interface operations */
|
|
static struct interface_operation fc_els_xchg_op[] = {
|
|
INTF_OP ( xfer_deliver, struct fc_els *, fc_els_rx ),
|
|
INTF_OP ( intf_close, struct fc_els *, fc_els_close ),
|
|
};
|
|
|
|
/** Fibre Channel ELS exchange interface descriptor */
|
|
static struct interface_descriptor fc_els_xchg_desc =
|
|
INTF_DESC ( struct fc_els, xchg, fc_els_xchg_op );
|
|
|
|
/** Fibre Channel ELS job control interface operations */
|
|
static struct interface_operation fc_els_job_op[] = {
|
|
INTF_OP ( intf_close, struct fc_els *, fc_els_close ),
|
|
};
|
|
|
|
/** Fibre Channel ELS job control interface descriptor */
|
|
static struct interface_descriptor fc_els_job_desc =
|
|
INTF_DESC ( struct fc_els, job, fc_els_job_op );
|
|
|
|
/**
|
|
* Fibre Channel ELS process
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
*/
|
|
static void fc_els_step ( struct fc_els *els ) {
|
|
int xchg_id;
|
|
int rc;
|
|
|
|
/* Sanity check */
|
|
assert ( fc_els_is_request ( els ) );
|
|
|
|
/* Create exchange */
|
|
if ( ( xchg_id = fc_xchg_originate ( &els->xchg, els->port,
|
|
&els->peer_port_id,
|
|
FC_TYPE_ELS ) ) < 0 ) {
|
|
rc = xchg_id;
|
|
DBGC ( els, FCELS_FMT " could not create exchange: %s\n",
|
|
FCELS_ARGS ( els ), strerror ( rc ) );
|
|
fc_els_close ( els, rc );
|
|
return;
|
|
}
|
|
|
|
/* Transmit request */
|
|
if ( ( rc = els->handler->tx ( els ) ) != 0 ) {
|
|
DBGC ( els, FCELS_FMT " could not transmit request: %s\n",
|
|
FCELS_ARGS ( els ), strerror ( rc ) );
|
|
fc_els_close ( els, rc );
|
|
return;
|
|
}
|
|
}
|
|
|
|
/** Fibre Channel ELS process descriptor */
|
|
static struct process_descriptor fc_els_process_desc =
|
|
PROC_DESC_ONCE ( struct fc_els, process, fc_els_step );
|
|
|
|
/**
|
|
* Create ELS transaction
|
|
*
|
|
* @v port Fibre Channel port
|
|
* @v port_id Local port ID
|
|
* @v peer_port_id Peer port ID
|
|
* @ret els Fibre Channel ELS transaction, or NULL
|
|
*/
|
|
static struct fc_els * fc_els_create ( struct fc_port *port,
|
|
struct fc_port_id *port_id,
|
|
struct fc_port_id *peer_port_id ) {
|
|
struct fc_els *els;
|
|
|
|
/* Allocate and initialise structure */
|
|
els = zalloc ( sizeof ( *els ) );
|
|
if ( ! els )
|
|
return NULL;
|
|
ref_init ( &els->refcnt, fc_els_free );
|
|
intf_init ( &els->job, &fc_els_job_desc, &els->refcnt );
|
|
intf_init ( &els->xchg, &fc_els_xchg_desc, &els->refcnt );
|
|
process_init_stopped ( &els->process, &fc_els_process_desc,
|
|
&els->refcnt );
|
|
els->port = fc_port_get ( port );
|
|
memcpy ( &els->port_id, port_id, sizeof ( els->port_id ) );
|
|
memcpy ( &els->peer_port_id, peer_port_id,
|
|
sizeof ( els->peer_port_id ) );
|
|
return els;
|
|
}
|
|
|
|
/**
|
|
* Create ELS request
|
|
*
|
|
* @v job Parent job-control interface
|
|
* @v port Fibre Channel port
|
|
* @v peer_port_id Peer port ID
|
|
* @v handler ELS handler
|
|
* @ret rc Return status code
|
|
*/
|
|
int fc_els_request ( struct interface *job, struct fc_port *port,
|
|
struct fc_port_id *peer_port_id,
|
|
struct fc_els_handler *handler ) {
|
|
struct fc_els *els;
|
|
|
|
/* Allocate and initialise structure */
|
|
els = fc_els_create ( port, &port->port_id, peer_port_id );
|
|
if ( ! els )
|
|
return -ENOMEM;
|
|
els->handler = handler;
|
|
els->flags = FC_ELS_REQUEST;
|
|
process_add ( &els->process );
|
|
|
|
/* Attach to parent job interface, mortalise self, and return */
|
|
intf_plug_plug ( &els->job, job );
|
|
ref_put ( &els->refcnt );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Create ELS response
|
|
*
|
|
* @v xchg Exchange interface
|
|
* @v port Fibre Channel port
|
|
* @v port_id Local port ID
|
|
* @v peer_port_id Peer port ID
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_respond ( struct interface *xchg, struct fc_port *port,
|
|
struct fc_port_id *port_id,
|
|
struct fc_port_id *peer_port_id ) {
|
|
struct fc_els *els;
|
|
|
|
/* Allocate and initialise structure */
|
|
els = fc_els_create ( port, port_id, peer_port_id );
|
|
if ( ! els )
|
|
return -ENOMEM;
|
|
|
|
/* Attach to exchange interface, mortalise self, and return */
|
|
intf_plug_plug ( &els->xchg, xchg );
|
|
ref_put ( &els->refcnt );
|
|
return 0;
|
|
}
|
|
|
|
/** Fibre Channel ELS responder */
|
|
struct fc_responder fc_els_responder __fc_responder = {
|
|
.type = FC_TYPE_ELS,
|
|
.respond = fc_els_respond,
|
|
};
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Unknown ELS handler
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Transmit unknown ELS request
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_unknown_tx ( struct fc_els *els __unused ) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/**
|
|
* Transmit unknown ELS response
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_unknown_tx_response ( struct fc_els *els ) {
|
|
struct fc_ls_rjt_frame ls_rjt;
|
|
|
|
/* Construct LS_RJT */
|
|
memset ( &ls_rjt, 0, sizeof ( ls_rjt ) );
|
|
ls_rjt.command = FC_ELS_LS_RJT;
|
|
ls_rjt.reason = FC_ELS_RJT_UNSUPPORTED;
|
|
|
|
/* Transmit LS_RJT */
|
|
return fc_els_tx ( els, &ls_rjt, sizeof ( ls_rjt ) );
|
|
}
|
|
|
|
/**
|
|
* Receive unknown ELS
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_unknown_rx ( struct fc_els *els, void *data, size_t len ) {
|
|
int rc;
|
|
|
|
DBGC ( els, FCELS_FMT ":\n", FCELS_ARGS ( els ) );
|
|
DBGC_HDA ( els, 0, data, len );
|
|
|
|
/* Transmit response, if applicable */
|
|
if ( ! fc_els_is_request ( els ) ) {
|
|
if ( ( rc = fc_els_unknown_tx_response ( els ) ) != 0 )
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Detect unknown ELS
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_unknown_detect ( struct fc_els *els __unused,
|
|
const void *data __unused,
|
|
size_t len __unused ) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/** Unknown ELS handler */
|
|
struct fc_els_handler fc_els_unknown_handler __fc_els_handler = {
|
|
.name = "UNKNOWN",
|
|
.tx = fc_els_unknown_tx,
|
|
.rx = fc_els_unknown_rx,
|
|
.detect = fc_els_unknown_detect,
|
|
};
|
|
|
|
/******************************************************************************
|
|
*
|
|
* FLOGI
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Transmit FLOGI
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_flogi_tx ( struct fc_els *els ) {
|
|
struct fc_login_frame flogi;
|
|
|
|
/* Construct FLOGI */
|
|
memset ( &flogi, 0, sizeof ( flogi ) );
|
|
flogi.command = fc_els_tx_command ( els, FC_ELS_FLOGI );
|
|
flogi.common.version = htons ( FC_LOGIN_VERSION );
|
|
flogi.common.credit = htons ( FC_LOGIN_DEFAULT_B2B );
|
|
flogi.common.flags = htons ( FC_LOGIN_CONTINUOUS_OFFSET );
|
|
flogi.common.mtu = htons ( FC_LOGIN_DEFAULT_MTU );
|
|
memcpy ( &flogi.port_wwn, &els->port->port_wwn,
|
|
sizeof ( flogi.port_wwn ) );
|
|
memcpy ( &flogi.node_wwn, &els->port->node_wwn,
|
|
sizeof ( flogi.node_wwn ) );
|
|
flogi.class3.flags = htons ( FC_LOGIN_CLASS_VALID |
|
|
FC_LOGIN_CLASS_SEQUENTIAL );
|
|
|
|
/* Transmit FLOGI */
|
|
return fc_els_tx ( els, &flogi, sizeof ( flogi ) );
|
|
}
|
|
|
|
/**
|
|
* Receive FLOGI
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_flogi_rx ( struct fc_els *els, void *data, size_t len ) {
|
|
struct fc_login_frame *flogi = data;
|
|
int has_fabric;
|
|
int rc;
|
|
|
|
/* Sanity check */
|
|
if ( len < sizeof ( *flogi ) ) {
|
|
DBGC ( els, FCELS_FMT " received underlength frame:\n",
|
|
FCELS_ARGS ( els ) );
|
|
DBGC_HDA ( els, 0, data, len );
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Extract parameters */
|
|
has_fabric = ( flogi->common.flags & htons ( FC_LOGIN_F_PORT ) );
|
|
DBGC ( els, FCELS_FMT " has node %s\n", FCELS_ARGS ( els ),
|
|
fc_ntoa ( &flogi->node_wwn ) );
|
|
DBGC ( els, FCELS_FMT " has port %s\n", FCELS_ARGS ( els ),
|
|
fc_ntoa ( &flogi->port_wwn ) );
|
|
if ( has_fabric ) {
|
|
DBGC ( els, FCELS_FMT " has fabric with", FCELS_ARGS ( els ) );
|
|
DBGC ( els, " local ID %s\n", fc_id_ntoa ( &els->port_id ) );
|
|
} else {
|
|
DBGC ( els, FCELS_FMT " has point-to-point link\n",
|
|
FCELS_ARGS ( els ) );
|
|
}
|
|
|
|
/* Log in port */
|
|
if ( ( rc = fc_port_login ( els->port, &els->port_id, &flogi->node_wwn,
|
|
&flogi->port_wwn, has_fabric ) ) != 0 ) {
|
|
DBGC ( els, FCELS_FMT " could not log in port: %s\n",
|
|
FCELS_ARGS ( els ), strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
/* Send any responses to the newly-assigned peer port ID, if
|
|
* applicable.
|
|
*/
|
|
if ( ! has_fabric ) {
|
|
memcpy ( &els->peer_port_id, &els->port->ptp_link_port_id,
|
|
sizeof ( els->peer_port_id ) );
|
|
}
|
|
|
|
/* Transmit response, if applicable */
|
|
if ( ! fc_els_is_request ( els ) ) {
|
|
if ( ( rc = fc_els_flogi_tx ( els ) ) != 0 )
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Detect FLOGI
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_flogi_detect ( struct fc_els *els __unused, const void *data,
|
|
size_t len __unused ) {
|
|
const struct fc_login_frame *flogi = data;
|
|
|
|
/* Check for FLOGI */
|
|
if ( flogi->command != FC_ELS_FLOGI )
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** FLOGI ELS handler */
|
|
struct fc_els_handler fc_els_flogi_handler __fc_els_handler = {
|
|
.name = "FLOGI",
|
|
.tx = fc_els_flogi_tx,
|
|
.rx = fc_els_flogi_rx,
|
|
.detect = fc_els_flogi_detect,
|
|
};
|
|
|
|
/**
|
|
* Create FLOGI request
|
|
*
|
|
* @v parent Parent interface
|
|
* @v port Fibre Channel port
|
|
* @ret rc Return status code
|
|
*/
|
|
int fc_els_flogi ( struct interface *parent, struct fc_port *port ) {
|
|
|
|
return fc_els_request ( parent, port, &fc_f_port_id,
|
|
&fc_els_flogi_handler );
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* PLOGI
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Transmit PLOGI
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_plogi_tx ( struct fc_els *els ) {
|
|
struct fc_login_frame plogi;
|
|
|
|
/* Construct PLOGI */
|
|
memset ( &plogi, 0, sizeof ( plogi ) );
|
|
plogi.command = fc_els_tx_command ( els, FC_ELS_PLOGI );
|
|
plogi.common.version = htons ( FC_LOGIN_VERSION );
|
|
plogi.common.credit = htons ( FC_LOGIN_DEFAULT_B2B );
|
|
plogi.common.flags = htons ( FC_LOGIN_CONTINUOUS_OFFSET );
|
|
plogi.common.mtu = htons ( FC_LOGIN_DEFAULT_MTU );
|
|
plogi.common.u.plogi.max_seq = htons ( FC_LOGIN_DEFAULT_MAX_SEQ );
|
|
plogi.common.u.plogi.rel_offs = htons ( FC_LOGIN_DEFAULT_REL_OFFS );
|
|
plogi.common.e_d_tov = htonl ( FC_LOGIN_DEFAULT_E_D_TOV );
|
|
memcpy ( &plogi.port_wwn, &els->port->port_wwn,
|
|
sizeof ( plogi.port_wwn ) );
|
|
memcpy ( &plogi.node_wwn, &els->port->node_wwn,
|
|
sizeof ( plogi.node_wwn ) );
|
|
plogi.class3.flags = htons ( FC_LOGIN_CLASS_VALID |
|
|
FC_LOGIN_CLASS_SEQUENTIAL );
|
|
plogi.class3.mtu = htons ( FC_LOGIN_DEFAULT_MTU );
|
|
plogi.class3.max_seq = htons ( FC_LOGIN_DEFAULT_MAX_SEQ );
|
|
plogi.class3.max_seq_per_xchg = 1;
|
|
|
|
/* Transmit PLOGI */
|
|
return fc_els_tx ( els, &plogi, sizeof ( plogi ) );
|
|
}
|
|
|
|
/**
|
|
* Receive PLOGI
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_plogi_rx ( struct fc_els *els, void *data, size_t len ) {
|
|
struct fc_login_frame *plogi = data;
|
|
struct fc_peer *peer;
|
|
int rc;
|
|
|
|
/* Sanity checks */
|
|
if ( len < sizeof ( *plogi ) ) {
|
|
DBGC ( els, FCELS_FMT " received underlength frame:\n",
|
|
FCELS_ARGS ( els ) );
|
|
DBGC_HDA ( els, 0, data, len );
|
|
rc = -EINVAL;
|
|
goto err_sanity;
|
|
}
|
|
if ( ! fc_link_ok ( &els->port->link ) ) {
|
|
DBGC ( els, FCELS_FMT " received while port link is down\n",
|
|
FCELS_ARGS ( els ) );
|
|
rc = -EINVAL;
|
|
goto err_sanity;
|
|
}
|
|
|
|
/* Extract parameters */
|
|
DBGC ( els, FCELS_FMT " has node %s\n", FCELS_ARGS ( els ),
|
|
fc_ntoa ( &plogi->node_wwn ) );
|
|
DBGC ( els, FCELS_FMT " has port %s as %s\n",
|
|
FCELS_ARGS ( els ), fc_ntoa ( &plogi->port_wwn ),
|
|
fc_id_ntoa ( &els->peer_port_id ) );
|
|
|
|
/* Get peer */
|
|
peer = fc_peer_get_wwn ( &plogi->port_wwn );
|
|
if ( ! peer ) {
|
|
DBGC ( els, FCELS_FMT " could not create peer\n",
|
|
FCELS_ARGS ( els ) );
|
|
rc = -ENOMEM;
|
|
goto err_peer_get_wwn;
|
|
}
|
|
|
|
/* Record login */
|
|
if ( ( rc = fc_peer_login ( peer, els->port,
|
|
&els->peer_port_id ) ) != 0 ) {
|
|
DBGC ( els, FCELS_FMT " could not log in peer: %s\n",
|
|
FCELS_ARGS ( els ), strerror ( rc ) );
|
|
goto err_login;
|
|
}
|
|
|
|
/* Transmit response, if applicable */
|
|
if ( ! fc_els_is_request ( els ) ) {
|
|
if ( ( rc = fc_els_plogi_tx ( els ) ) != 0 )
|
|
goto err_plogi_tx;
|
|
}
|
|
|
|
/* Drop temporary reference to peer */
|
|
fc_peer_put ( peer );
|
|
|
|
return 0;
|
|
|
|
err_plogi_tx:
|
|
err_login:
|
|
fc_peer_put ( peer );
|
|
err_peer_get_wwn:
|
|
err_sanity:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Detect PLOGI
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_plogi_detect ( struct fc_els *els __unused, const void *data,
|
|
size_t len __unused ) {
|
|
const struct fc_login_frame *plogi = data;
|
|
|
|
/* Check for PLOGI */
|
|
if ( plogi->command != FC_ELS_PLOGI )
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** PLOGI ELS handler */
|
|
struct fc_els_handler fc_els_plogi_handler __fc_els_handler = {
|
|
.name = "PLOGI",
|
|
.tx = fc_els_plogi_tx,
|
|
.rx = fc_els_plogi_rx,
|
|
.detect = fc_els_plogi_detect,
|
|
};
|
|
|
|
/**
|
|
* Create PLOGI request
|
|
*
|
|
* @v parent Parent interface
|
|
* @v port Fibre Channel port
|
|
* @v peer_port_id Peer port ID
|
|
* @ret rc Return status code
|
|
*/
|
|
int fc_els_plogi ( struct interface *parent, struct fc_port *port,
|
|
struct fc_port_id *peer_port_id ) {
|
|
|
|
return fc_els_request ( parent, port, peer_port_id,
|
|
&fc_els_plogi_handler );
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* LOGO
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Transmit LOGO request
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_logo_tx ( struct fc_els *els ) {
|
|
struct fc_logout_request_frame logo;
|
|
|
|
/* Construct LOGO */
|
|
memset ( &logo, 0, sizeof ( logo ) );
|
|
logo.command = FC_ELS_LOGO;
|
|
memcpy ( &logo.port_id, &els->port->port_id, sizeof ( logo.port_id ) );
|
|
memcpy ( &logo.port_wwn, &els->port->port_wwn,
|
|
sizeof ( logo.port_wwn ) );
|
|
|
|
/* Transmit LOGO */
|
|
return fc_els_tx ( els, &logo, sizeof ( logo ) );
|
|
}
|
|
|
|
/**
|
|
* Transmit LOGO response
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_logo_tx_response ( struct fc_els *els ) {
|
|
struct fc_logout_response_frame logo;
|
|
|
|
/* Construct LOGO */
|
|
memset ( &logo, 0, sizeof ( logo ) );
|
|
logo.command = FC_ELS_LS_ACC;
|
|
|
|
/* Transmit LOGO */
|
|
return fc_els_tx ( els, &logo, sizeof ( logo ) );
|
|
}
|
|
|
|
/**
|
|
* Log out individual peer or whole port as applicable
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v port_id Peer port ID
|
|
*/
|
|
static void fc_els_logo_logout ( struct fc_els *els,
|
|
struct fc_port_id *peer_port_id ) {
|
|
struct fc_peer *peer;
|
|
|
|
if ( ( memcmp ( peer_port_id, &fc_f_port_id,
|
|
sizeof ( *peer_port_id ) ) == 0 ) ||
|
|
( memcmp ( peer_port_id, &els->port->port_id,
|
|
sizeof ( *peer_port_id ) ) == 0 ) ) {
|
|
fc_port_logout ( els->port, 0 );
|
|
} else {
|
|
peer = fc_peer_get_port_id ( els->port, peer_port_id );
|
|
if ( peer ) {
|
|
fc_peer_logout ( peer, 0 );
|
|
fc_peer_put ( peer );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Receive LOGO request
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_logo_rx_request ( struct fc_els *els, void *data,
|
|
size_t len ) {
|
|
struct fc_logout_request_frame *logo = data;
|
|
int rc;
|
|
|
|
/* Sanity check */
|
|
if ( len < sizeof ( *logo ) ) {
|
|
DBGC ( els, FCELS_FMT " received underlength frame:\n",
|
|
FCELS_ARGS ( els ) );
|
|
DBGC_HDA ( els, 0, data, len );
|
|
return -EINVAL;
|
|
}
|
|
|
|
DBGC ( els, FCELS_FMT " has port %s as %s\n", FCELS_ARGS ( els ),
|
|
fc_ntoa ( &logo->port_wwn ), fc_id_ntoa ( &logo->port_id ) );
|
|
|
|
/* Log out individual peer or whole port as applicable */
|
|
fc_els_logo_logout ( els, &logo->port_id );
|
|
|
|
/* Transmit repsonse */
|
|
if ( ( rc = fc_els_logo_tx_response ( els ) ) != 0 )
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Receive LOGO response
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_logo_rx_response ( struct fc_els *els, void *data __unused,
|
|
size_t len __unused ) {
|
|
|
|
/* Log out individual peer or whole port as applicable */
|
|
fc_els_logo_logout ( els, &els->peer_port_id );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Receive LOGO
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_logo_rx ( struct fc_els *els, void *data, size_t len ) {
|
|
|
|
if ( fc_els_is_request ( els ) ) {
|
|
return fc_els_logo_rx_response ( els, data, len );
|
|
} else {
|
|
return fc_els_logo_rx_request ( els, data, len );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Detect LOGO
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_logo_detect ( struct fc_els *els __unused, const void *data,
|
|
size_t len __unused ) {
|
|
const struct fc_logout_request_frame *logo = data;
|
|
|
|
/* Check for LOGO */
|
|
if ( logo->command != FC_ELS_LOGO )
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** LOGO ELS handler */
|
|
struct fc_els_handler fc_els_logo_handler __fc_els_handler = {
|
|
.name = "LOGO",
|
|
.tx = fc_els_logo_tx,
|
|
.rx = fc_els_logo_rx,
|
|
.detect = fc_els_logo_detect,
|
|
};
|
|
|
|
/**
|
|
* Create LOGO request
|
|
*
|
|
* @v parent Parent interface
|
|
* @v port Fibre Channel port
|
|
* @v peer_port_id Peer port ID
|
|
* @ret rc Return status code
|
|
*/
|
|
int fc_els_logo ( struct interface *parent, struct fc_port *port,
|
|
struct fc_port_id *peer_port_id ) {
|
|
|
|
return fc_els_request ( parent, port, peer_port_id,
|
|
&fc_els_logo_handler );
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* PRLI
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Find PRLI descriptor
|
|
*
|
|
* @v type Upper-layer protocol type
|
|
* @ret descriptor PRLI descriptor, or NULL
|
|
*/
|
|
static struct fc_els_prli_descriptor *
|
|
fc_els_prli_descriptor ( unsigned int type ) {
|
|
struct fc_els_prli_descriptor *descriptor;
|
|
|
|
for_each_table_entry ( descriptor, FC_ELS_PRLI_DESCRIPTORS ) {
|
|
if ( descriptor->type == type )
|
|
return descriptor;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Transmit PRLI
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v descriptor ELS PRLI descriptor
|
|
* @v param Service parameters
|
|
* @ret rc Return status code
|
|
*/
|
|
int fc_els_prli_tx ( struct fc_els *els,
|
|
struct fc_els_prli_descriptor *descriptor, void *param ) {
|
|
struct {
|
|
struct fc_prli_frame frame;
|
|
uint8_t param[descriptor->param_len];
|
|
} __attribute__ (( packed )) prli;
|
|
struct fc_ulp *ulp;
|
|
int rc;
|
|
|
|
/* Get ULP */
|
|
ulp = fc_ulp_get_port_id_type ( els->port, &els->peer_port_id,
|
|
descriptor->type );
|
|
if ( ! ulp ) {
|
|
rc = -ENOMEM;
|
|
goto err_get_port_id_type;
|
|
}
|
|
|
|
/* Build frame for transmission */
|
|
memset ( &prli, 0, sizeof ( prli ) );
|
|
prli.frame.command = fc_els_tx_command ( els, FC_ELS_PRLI );
|
|
prli.frame.page_len =
|
|
( sizeof ( prli.frame.page ) + sizeof ( prli.param ) );
|
|
prli.frame.len = htons ( sizeof ( prli ) );
|
|
prli.frame.page.type = descriptor->type;
|
|
if ( fc_els_is_request ( els ) ) {
|
|
prli.frame.page.flags |= htons ( FC_PRLI_ESTABLISH );
|
|
} else if ( fc_link_ok ( &ulp->link ) ) {
|
|
prli.frame.page.flags |= htons ( FC_PRLI_ESTABLISH |
|
|
FC_PRLI_RESPONSE_SUCCESS );
|
|
}
|
|
memcpy ( &prli.param, param, sizeof ( prli.param ) );
|
|
|
|
/* Transmit frame */
|
|
if ( ( rc = fc_els_tx ( els, &prli, sizeof ( prli ) ) ) != 0 )
|
|
goto err_tx;
|
|
|
|
/* Drop temporary reference to ULP */
|
|
fc_ulp_put ( ulp );
|
|
|
|
return 0;
|
|
|
|
err_tx:
|
|
fc_ulp_put ( ulp );
|
|
err_get_port_id_type:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Receive PRLI
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v descriptor ELS PRLI descriptor
|
|
* @v frame ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
int fc_els_prli_rx ( struct fc_els *els,
|
|
struct fc_els_prli_descriptor *descriptor,
|
|
void *data, size_t len ) {
|
|
struct {
|
|
struct fc_prli_frame frame;
|
|
uint8_t param[descriptor->param_len];
|
|
} __attribute__ (( packed )) *prli = data;
|
|
struct fc_ulp *ulp;
|
|
int rc;
|
|
|
|
/* Sanity check */
|
|
if ( len < sizeof ( *prli ) ) {
|
|
DBGC ( els, FCELS_FMT " received underlength frame:\n",
|
|
FCELS_ARGS ( els ) );
|
|
DBGC_HDA ( els, 0, data, len );
|
|
rc = -EINVAL;
|
|
goto err_sanity;
|
|
}
|
|
|
|
DBGC ( els, FCELS_FMT " has parameters:\n", FCELS_ARGS ( els ) );
|
|
DBGC_HDA ( els, 0, prli->param, sizeof ( prli->param ) );
|
|
|
|
/* Get ULP */
|
|
ulp = fc_ulp_get_port_id_type ( els->port, &els->peer_port_id,
|
|
descriptor->type );
|
|
if ( ! ulp ) {
|
|
rc = -ENOMEM;
|
|
goto err_get_port_id_type;
|
|
}
|
|
|
|
/* Sanity check */
|
|
if ( ! fc_link_ok ( &ulp->peer->link ) ) {
|
|
DBGC ( els, FCELS_FMT " received while peer link is down\n",
|
|
FCELS_ARGS ( els ) );
|
|
rc = -EINVAL;
|
|
goto err_link;
|
|
}
|
|
|
|
/* Log in ULP, if applicable */
|
|
if ( prli->frame.page.flags & htons ( FC_PRLI_ESTABLISH ) ) {
|
|
if ( ( rc = fc_ulp_login ( ulp, prli->param,
|
|
sizeof ( prli->param ),
|
|
fc_els_is_request ( els ) ) ) != 0 ){
|
|
DBGC ( els, FCELS_FMT " could not log in ULP: %s\n",
|
|
FCELS_ARGS ( els ), strerror ( rc ) );
|
|
goto err_login;
|
|
}
|
|
} else {
|
|
if ( fc_els_is_request ( els ) ) {
|
|
fc_ulp_logout ( ulp, -EACCES );
|
|
} else {
|
|
/* This is just an information-gathering PRLI; do not
|
|
* log in or out
|
|
*/
|
|
}
|
|
}
|
|
|
|
/* Transmit response, if applicable */
|
|
if ( ! fc_els_is_request ( els ) ) {
|
|
if ( ( rc = els->handler->tx ( els ) ) != 0 )
|
|
goto err_tx;
|
|
}
|
|
|
|
/* Drop temporary reference to ULP */
|
|
fc_ulp_put ( ulp );
|
|
|
|
return 0;
|
|
|
|
err_tx:
|
|
err_login:
|
|
err_link:
|
|
fc_ulp_put ( ulp );
|
|
err_get_port_id_type:
|
|
err_sanity:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Detect PRLI
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v descriptor ELS PRLI descriptor
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
int fc_els_prli_detect ( struct fc_els *els __unused,
|
|
struct fc_els_prli_descriptor *descriptor,
|
|
const void *data, size_t len ) {
|
|
const struct {
|
|
struct fc_prli_frame frame;
|
|
uint8_t param[descriptor->param_len];
|
|
} __attribute__ (( packed )) *prli = data;
|
|
|
|
/* Check for PRLI */
|
|
if ( prli->frame.command != FC_ELS_PRLI )
|
|
return -EINVAL;
|
|
|
|
/* Check for sufficient length to contain service parameter page */
|
|
if ( len < sizeof ( *prli ) )
|
|
return -EINVAL;
|
|
|
|
/* Check for upper-layer protocol type */
|
|
if ( prli->frame.page.type != descriptor->type )
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Create PRLI request
|
|
*
|
|
* @v parent Parent interface
|
|
* @v port Fibre Channel port
|
|
* @v peer_port_id Peer port ID
|
|
* @v type Upper-layer protocol type
|
|
* @ret rc Return status code
|
|
*/
|
|
int fc_els_prli ( struct interface *parent, struct fc_port *port,
|
|
struct fc_port_id *peer_port_id, unsigned int type ) {
|
|
struct fc_els_prli_descriptor *descriptor;
|
|
|
|
/* Find a PRLI descriptor */
|
|
descriptor = fc_els_prli_descriptor ( type );
|
|
if ( ! descriptor )
|
|
return -ENOTSUP;
|
|
|
|
return fc_els_request ( parent, port, peer_port_id,
|
|
descriptor->handler );
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* RTV
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Transmit RTV response
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_rtv_tx_response ( struct fc_els *els ) {
|
|
struct fc_rtv_response_frame rtv;
|
|
|
|
/* Construct RTV */
|
|
memset ( &rtv, 0, sizeof ( rtv ) );
|
|
rtv.command = FC_ELS_LS_ACC;
|
|
rtv.e_d_tov = htonl ( FC_LOGIN_DEFAULT_E_D_TOV );
|
|
|
|
/* Transmit RTV */
|
|
return fc_els_tx ( els, &rtv, sizeof ( rtv ) );
|
|
}
|
|
|
|
/**
|
|
* Receive RTV
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_rtv_rx ( struct fc_els *els, void *data __unused,
|
|
size_t len __unused ) {
|
|
int rc;
|
|
|
|
DBGC ( els, FCELS_FMT "\n", FCELS_ARGS ( els ) );
|
|
|
|
/* Transmit response */
|
|
if ( ! fc_els_is_request ( els ) ) {
|
|
if ( ( rc = fc_els_rtv_tx_response ( els ) ) != 0 )
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Detect RTV
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_rtv_detect ( struct fc_els *els __unused, const void *data,
|
|
size_t len __unused ) {
|
|
const struct fc_rtv_request_frame *rtv = data;
|
|
|
|
/* Check for RTV */
|
|
if ( rtv->command != FC_ELS_RTV )
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** RTV ELS handler */
|
|
struct fc_els_handler fc_els_rtv_handler __fc_els_handler = {
|
|
.name = "RTV",
|
|
.tx = fc_els_unknown_tx,
|
|
.rx = fc_els_rtv_rx,
|
|
.detect = fc_els_rtv_detect,
|
|
};
|
|
|
|
/******************************************************************************
|
|
*
|
|
* ECHO
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/** ECHO request data */
|
|
struct fc_echo_request_frame {
|
|
/** ECHO frame header */
|
|
struct fc_echo_frame_header echo;
|
|
/** Magic marker */
|
|
uint32_t magic;
|
|
} __attribute__ (( packed ));
|
|
|
|
/** ECHO magic marker */
|
|
#define FC_ECHO_MAGIC 0x69505845
|
|
|
|
/**
|
|
* Transmit ECHO
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_echo_tx ( struct fc_els *els ) {
|
|
struct fc_echo_request_frame echo;
|
|
|
|
/* Construct ECHO */
|
|
memset ( &echo, 0, sizeof ( echo ) );
|
|
echo.echo.command = FC_ELS_ECHO;
|
|
echo.magic = htonl ( FC_ECHO_MAGIC );
|
|
|
|
/* Transmit ECHO */
|
|
return fc_els_tx ( els, &echo, sizeof ( echo ) );
|
|
}
|
|
|
|
/**
|
|
* Receive ECHO request
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_echo_rx_request ( struct fc_els *els, void *data,
|
|
size_t len ) {
|
|
struct {
|
|
struct fc_echo_frame_header echo;
|
|
char payload[ len - sizeof ( struct fc_echo_frame_header ) ];
|
|
} *echo = data;
|
|
int rc;
|
|
|
|
DBGC ( els, FCELS_FMT "\n", FCELS_ARGS ( els ) );
|
|
|
|
/* Transmit response */
|
|
echo->echo.command = FC_ELS_LS_ACC;
|
|
if ( ( rc = fc_els_tx ( els, echo, sizeof ( *echo ) ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Nothing to do */
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Receive ECHO response
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_echo_rx_response ( struct fc_els *els, void *data,
|
|
size_t len ) {
|
|
struct fc_echo_request_frame *echo = data;
|
|
|
|
DBGC ( els, FCELS_FMT "\n", FCELS_ARGS ( els ) );
|
|
|
|
/* Check response is correct */
|
|
if ( ( len != sizeof ( *echo ) ) ||
|
|
( echo->magic != htonl ( FC_ECHO_MAGIC ) ) ) {
|
|
DBGC ( els, FCELS_FMT " received bad echo response\n",
|
|
FCELS_ARGS ( els ) );
|
|
DBGC_HDA ( els, 0, data, len );
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Receive ECHO
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_echo_rx ( struct fc_els *els, void *data, size_t len ) {
|
|
|
|
if ( fc_els_is_request ( els ) ) {
|
|
return fc_els_echo_rx_response ( els, data, len );
|
|
} else {
|
|
return fc_els_echo_rx_request ( els, data, len );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Detect ECHO
|
|
*
|
|
* @v els Fibre Channel ELS transaction
|
|
* @v data ELS frame
|
|
* @v len Length of ELS frame
|
|
* @ret rc Return status code
|
|
*/
|
|
static int fc_els_echo_detect ( struct fc_els *els __unused, const void *data,
|
|
size_t len __unused ) {
|
|
const struct fc_echo_frame_header *echo = data;
|
|
|
|
/* Check for ECHO */
|
|
if ( echo->command != FC_ELS_ECHO )
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** ECHO ELS handler */
|
|
struct fc_els_handler fc_els_echo_handler __fc_els_handler = {
|
|
.name = "ECHO",
|
|
.tx = fc_els_echo_tx,
|
|
.rx = fc_els_echo_rx,
|
|
.detect = fc_els_echo_detect,
|
|
};
|