mirror of
https://github.com/ipxe/ipxe
synced 2025-12-23 21:41:43 +03:00
The port ID assigned by the FLOGI response is implicit in the destination ID used for the response (which will differ from the source ID used for the corresponding request). Signed-off-by: Michael Brown <mcb30@ipxe.org>
1194 lines
30 KiB
C
1194 lines
30 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 ( * rx ) ( struct fc_els *els, const void *data, size_t len );
|
|
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 */
|
|
rx = ( fc_els_is_request ( els ) ?
|
|
els->handler->rx_response : els->handler->rx_request );
|
|
if ( ( rc = 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;
|
|
}
|
|
|
|
/* Free I/O buffer. Do this before transmitting response to
|
|
* minimise memory consumption.
|
|
*/
|
|
free_iob ( iob_disown ( iobuf ) );
|
|
|
|
/* Transmit response if applicable */
|
|
if ( ( ! fc_els_is_request ( els ) ) &&
|
|
( ( rc = els->handler->tx_response ( els ) ) != 0 ) ) {
|
|
DBGC ( els, FCELS_FMT " could not transmit response: %s\n",
|
|
FCELS_ARGS ( els ), strerror ( rc ) );
|
|
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 process Process
|
|
*/
|
|
static void fc_els_step ( struct process *process ) {
|
|
struct fc_els *els =
|
|
container_of ( process, struct fc_els, process );
|
|
int rc;
|
|
|
|
/* Sanity check */
|
|
assert ( fc_els_is_request ( els ) );
|
|
|
|
/* Stop process */
|
|
process_del ( &els->process );
|
|
|
|
/* Create exchange */
|
|
if ( ( rc = fc_xchg_originate ( &els->xchg, els->port,
|
|
&els->peer_port_id,
|
|
FC_TYPE_ELS ) ) != 0 ) {
|
|
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_request ( els ) ) != 0 ) {
|
|
DBGC ( els, FCELS_FMT " could not transmit request: %s\n",
|
|
FCELS_ARGS ( els ), strerror ( rc ) );
|
|
fc_els_close ( els, rc );
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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_step, &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_request ( 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 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_unknown_rx_request ( struct fc_els *els, const void *data,
|
|
size_t len ) {
|
|
|
|
DBGC ( els, FCELS_FMT ":\n", FCELS_ARGS ( els ) );
|
|
DBGC_HDA ( els, 0, data, len );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Receive unknown ELS 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_unknown_rx_response ( struct fc_els *els __unused,
|
|
const void *data __unused,
|
|
size_t len __unused ) {
|
|
assert ( 0 );
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* 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_request = fc_els_unknown_tx_request,
|
|
.tx_response = fc_els_unknown_tx_response,
|
|
.rx_request = fc_els_unknown_rx_request,
|
|
.rx_response = fc_els_unknown_rx_response,
|
|
.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, const void *data,
|
|
size_t len ) {
|
|
const 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 ) );
|
|
}
|
|
|
|
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_request = fc_els_flogi_tx,
|
|
.tx_response = fc_els_flogi_tx,
|
|
.rx_request = fc_els_flogi_rx,
|
|
.rx_response = 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, const void *data,
|
|
size_t len ) {
|
|
const 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->node_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;
|
|
}
|
|
|
|
/* Drop temporary reference to peer */
|
|
fc_peer_put ( peer );
|
|
|
|
return 0;
|
|
|
|
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_request = fc_els_plogi_tx,
|
|
.tx_response = fc_els_plogi_tx,
|
|
.rx_request = fc_els_plogi_rx,
|
|
.rx_response = 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_request ( 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,
|
|
const 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, const void *data,
|
|
size_t len ) {
|
|
const struct fc_logout_request_frame *logo = data;
|
|
|
|
/* 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 );
|
|
|
|
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,
|
|
const void *data __unused,
|
|
size_t len __unused ) {
|
|
|
|
/* Log out individual peer or whole port as applicable */
|
|
fc_els_logo_logout ( els, &els->port_id );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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_request = fc_els_logo_tx_request,
|
|
.tx_response = fc_els_logo_tx_response,
|
|
.rx_request = fc_els_logo_rx_request,
|
|
.rx_response = fc_els_logo_rx_response,
|
|
.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,
|
|
const void *data, size_t len ) {
|
|
const 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
|
|
*/
|
|
}
|
|
}
|
|
|
|
/* Drop temporary reference to ULP */
|
|
fc_ulp_put ( ulp );
|
|
|
|
return 0;
|
|
|
|
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 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_rtv_rx_request ( struct fc_els *els,
|
|
const void *data __unused,
|
|
size_t len __unused ) {
|
|
|
|
DBGC ( els, FCELS_FMT "\n", FCELS_ARGS ( els ) );
|
|
|
|
/* Nothing to do */
|
|
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_request = fc_els_unknown_tx_request,
|
|
.tx_response = fc_els_rtv_tx_response,
|
|
.rx_request = fc_els_rtv_rx_request,
|
|
.rx_response = fc_els_unknown_rx_response,
|
|
.detect = fc_els_rtv_detect,
|
|
};
|