Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / ipxe / src / net / fcels.c
diff --git a/qemu/roms/ipxe/src/net/fcels.c b/qemu/roms/ipxe/src/net/fcels.c
new file mode 100644 (file)
index 0000000..1cfe907
--- /dev/null
@@ -0,0 +1,1339 @@
+/*
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, 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,
+};