]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[fc] Add support for Fibre Channel name server lookups
authorMichael Brown <mcb30@ipxe.org>
Thu, 4 Nov 2010 03:31:15 +0000 (03:31 +0000)
committerMichael Brown <mcb30@ipxe.org>
Mon, 8 Nov 2010 03:35:36 +0000 (03:35 +0000)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/errfile.h
src/include/ipxe/fc.h
src/include/ipxe/fcns.h [new file with mode: 0644]
src/net/fc.c
src/net/fcns.c [new file with mode: 0644]

index 10bc49d8d5161757245f1bf9fa9de3207665abd7..2255f8a3056bacfd383ba82bccd396063493c650 100644 (file)
@@ -189,6 +189,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #define ERRFILE_fcels                  ( ERRFILE_NET | 0x002c0000 )
 #define ERRFILE_fcp                    ( ERRFILE_NET | 0x002d0000 )
 #define ERRFILE_fcoe                   ( ERRFILE_NET | 0x002e0000 )
+#define ERRFILE_fcns                   ( ERRFILE_NET | 0x002f0000 )
 
 #define ERRFILE_image                ( ERRFILE_IMAGE | 0x00000000 )
 #define ERRFILE_elf                  ( ERRFILE_IMAGE | 0x00010000 )
index 8dbc19e7f27f5353eb2cd888552387dda31ce8e6..3a80d56c94262630eed7b4860c63996b0fd33244 100644 (file)
@@ -64,6 +64,7 @@ struct sockaddr_fc {
 
 extern struct fc_port_id fc_empty_port_id;
 extern struct fc_port_id fc_f_port_id;
+extern struct fc_port_id fc_gs_port_id;
 extern struct fc_port_id fc_ptp_low_port_id;
 extern struct fc_port_id fc_ptp_high_port_id;
 
@@ -190,6 +191,7 @@ enum fc_type {
        FC_TYPE_BLS = 0x00,             /**< Basic Link Service */
        FC_TYPE_ELS = 0x01,             /**< Extended Link Service */
        FC_TYPE_FCP = 0x08,             /**< Fibre Channel Protocol */
+       FC_TYPE_CT  = 0x20,             /**< Common Transport */
 };
 
 /** Fibre Channel Frame Control - Exchange and Sequence */
@@ -277,6 +279,9 @@ struct fc_port {
        /** Link port ID (for point-to-point links only) */
        struct fc_port_id ptp_link_port_id;
 
+       /** Name server PLOGI interface */
+       struct interface ns_plogi;
+
        /** List of active exchanges */
        struct list_head xchgs;
 };
@@ -285,6 +290,8 @@ struct fc_port {
 enum fc_port_flags {
        /** Port is attached to a fabric */
        FC_PORT_HAS_FABRIC = 0x0001,
+       /** Port is logged in to a name server */
+       FC_PORT_HAS_NS = 0x0002,
 };
 
 /**
diff --git a/src/include/ipxe/fcns.h b/src/include/ipxe/fcns.h
new file mode 100644 (file)
index 0000000..e25d9b9
--- /dev/null
@@ -0,0 +1,217 @@
+#ifndef _IPXE_FCNS_H
+#define _IPXE_FCNS_H
+
+/**
+ * @file
+ *
+ * Fibre Channel name server lookups
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <ipxe/fc.h>
+
+/** A Fibre Channel Common Transport header */
+struct fc_ct_header {
+       /** Revision */
+       uint8_t revision;
+       /** Original requestor ID */
+       struct fc_port_id in_id;
+       /** Generic service type */
+       uint8_t type;
+       /** Generic service subtype */
+       uint8_t subtype;
+       /** Options */
+       uint8_t options;
+       /** Reserved */
+       uint8_t reserved;
+       /** Command/response code */
+       uint16_t code;
+       /** Maximum/residual size */
+       uint16_t size;
+       /** Fragment ID */
+       uint8_t fragment;
+       /** Reason code */
+       uint8_t reason;
+       /** Reason code explanation */
+       uint8_t explanation;
+       /** Vendor specific */
+       uint8_t vendor;
+} __attribute__ (( packed ));
+
+/** Fibre Channel Common Transport revision */
+#define FC_CT_REVISION 1
+
+/** Fibre Channel generic service type */
+enum fc_gs_type {
+       /** Directory service */
+       FC_GS_TYPE_DS = 0xfc,
+};
+
+/** Fibre Channel generic service response codes */
+enum fc_gs_response_code {
+       /** Accepted */
+       FC_GS_ACCEPT = 0x8002,
+       /** Rejected */
+       FC_GS_REJECT = 0x8001,
+};
+
+/** Fibre Channel generic service rejection reason codes */
+enum fc_gs_reason_code {
+       /** Invalid command code */
+       FC_GS_BAD_COMMAND = 0x01,
+       /** Invalid version level */
+       FC_GS_BAD_VERSION = 0x02,
+       /** Logical error */
+       FC_GS_ERROR = 0x03,
+       /** Invalid CT_IU size */
+       FC_GS_BAD_SIZE = 0x04,
+       /** Logical busy */
+       FC_GS_BUSY = 0x05,
+       /** Protocol error */
+       FC_GS_EPROTO = 0x07,
+       /** Unable to perform command request */
+       FC_GS_UNABLE = 0x09,
+       /** Command not supported */
+       FC_GS_ENOTSUP = 0x0b,
+       /** Server not available */
+       FC_GS_UNAVAILABLE = 0x0d,
+       /** Session could not be established */
+       FC_GS_SESSION = 0x0e,
+};
+
+/** Fibre Channel directory service subtype */
+enum fc_ds_subtype {
+       /** Name server */
+       FC_DS_SUBTYPE_NAME = 0x02,
+};
+
+/** Fibre Channel name server commands */
+enum fc_ns_command_nibble {
+       /** Get */
+       FC_NS_GET = 0x1,
+       /** Register */
+       FC_NS_REGISTER = 0x2,
+       /** De-register */
+       FC_NS_DEREGISTER = 0x3,
+};
+
+/** Fibre Channel name server objects */
+enum fc_ns_object_nibble {
+       /** Port ID */
+       FC_NS_PORT_ID = 0x1,
+       /** Port name */
+       FC_NS_PORT_NAME = 0x2,
+       /** Node name */
+       FC_NS_NODE_NAME = 0x3,
+       /** FC-4 types */
+       FC_NS_FC4_TYPES = 0x7,
+       /** Symbolic port name */
+       FC_NS_SYM_PORT_NAME = 0x8,
+       /** Symbolic node name */
+       FC_NS_SYM_NODE_NAME = 0x9,
+       /** FC-4 features */
+       FC_NS_FC4_FEATURES = 0xf,
+};
+
+/** Construct Fibre Channel name server command code
+ *
+ * @v command          Name server command
+ * @v key              Name server key
+ * @v value            Name server value
+ * @ret code           Name server command code
+ */
+#define FC_NS_CODE( command, key, value )                              \
+       ( ( (command) << 8 ) | ( (key) << 4 ) | ( (value) << 0 ) )
+
+/** Construct Fibre Channel name server "get" command code
+ *
+ * @v key              Name server key
+ * @v value            Name server value to get
+ * @ret code           Name server command code
+ */
+#define FC_NS_GET( key, value ) FC_NS_CODE ( FC_NS_GET, key, value )
+
+/** Construct Fibre Channel name server "register" command code
+ *
+ * @v key              Name server key
+ * @v value            Name server value to register
+ * @ret code           Name server command code
+ */
+#define FC_NS_REGISTER( key, value ) FC_NS_CODE ( FC_NS_REGISTER, key, value )
+
+/** Extract Fibre Channel name server command
+ *
+ * @v code             Name server command code
+ * @ret command                Name server command
+ */
+#define FC_NS_COMMAND( code ) ( ( (code) >> 8 ) & 0xf )
+
+/** Extract Fibre Channel name server key
+ *
+ * @v code             Name server command code
+ * @ret key            Name server key
+ */
+#define FC_NS_KEY( code ) ( ( (code) >> 4 ) & 0xf )
+
+/** Extract Fibre Channel name server value
+ *
+ * @v code             Name server command code
+ * @ret value          NAme server value
+ */
+#define FC_NS_VALUE( code ) ( ( (code) >> 0 ) & 0xf )
+
+/** A Fibre Channel name server port ID */
+struct fc_ns_port_id {
+       /** Reserved */
+       uint8_t reserved;
+       /** Port ID */
+       struct fc_port_id port_id;
+} __attribute__ (( packed ));
+
+/** A Fibre Channel name server GID_PN request */
+struct fc_ns_gid_pn_request {
+       /** Common Transport header */
+       struct fc_ct_header ct;
+       /** Port name */
+       struct fc_name port_wwn;
+} __attribute__ (( packed ));
+
+/** A Fibre Channel name server request */
+union fc_ns_request {
+       /** Get ID by port name */
+       struct fc_ns_gid_pn_request gid_pn;
+};
+
+/** A Fibre Channel name server rejection response */
+struct fc_ns_reject_response {
+       /** Common Transport header */
+       struct fc_ct_header ct;
+} __attribute__ (( packed ));
+
+/** A Fibre Channel name server GID_PN response */
+struct fc_ns_gid_pn_response {
+       /** Common Transport header */
+       struct fc_ct_header ct;
+       /** Port ID */
+       struct fc_ns_port_id port_id;
+} __attribute__ (( packed ));
+
+/** A Fibre Channel name server response */
+union fc_ns_response {
+       /** Common Transport header */
+       struct fc_ct_header ct;
+       /** Rejection */
+       struct fc_ns_reject_response reject;
+       /** Get ID by port name */
+       struct fc_ns_gid_pn_response gid_pn;
+};
+
+extern int fc_ns_query ( struct fc_peer *peer, struct fc_port *port,
+                        int ( * done ) ( struct fc_peer *peer,
+                                         struct fc_port *port,
+                                         struct fc_port_id *peer_port_id ) );
+
+#endif /* _IPXE_FCNS_H */
index d321c1fd9aa2b93fd48084632b834a1767f7fd46..eecb1adb5d00684666cc398d175c61ad04510c91 100644 (file)
@@ -35,6 +35,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/iobuf.h>
 #include <ipxe/fc.h>
 #include <ipxe/fcels.h>
+#include <ipxe/fcns.h>
 
 /** @file
  *
@@ -61,6 +62,9 @@ struct fc_port_id fc_empty_port_id = { .bytes = { 0x00, 0x00, 0x00 } };
 /** F_Port contoller port ID */
 struct fc_port_id fc_f_port_id = { .bytes = { 0xff, 0xff, 0xfe } };
 
+/** Generic services port ID */
+struct fc_port_id fc_gs_port_id = { .bytes = { 0xff, 0xff, 0xfc } };
+
 /** Point-to-point low port ID */
 struct fc_port_id fc_ptp_low_port_id = { .bytes = { 0x01, 0x01, 0x01 } };
 
@@ -464,14 +468,24 @@ static int fc_xchg_tx ( struct fc_exchange *xchg, struct io_buffer *iobuf,
        }
 
        /* Calculate routing control */
-       if ( xchg->type == FC_TYPE_ELS ) {
+       switch ( xchg->type ) {
+       case FC_TYPE_ELS:
                r_ctl = FC_R_CTL_ELS;
                if ( meta->flags & XFER_FL_RESPONSE ) {
                        r_ctl |= FC_R_CTL_SOL_CTRL;
                } else {
                        r_ctl |= FC_R_CTL_UNSOL_CTRL;
                }
-       } else {
+               break;
+       case FC_TYPE_CT:
+               r_ctl = FC_R_CTL_DATA;
+               if ( meta->flags & XFER_FL_RESPONSE ) {
+                       r_ctl |= FC_R_CTL_SOL_CTRL;
+               } else {
+                       r_ctl |= FC_R_CTL_UNSOL_CTRL;
+               }
+               break;
+       default:
                r_ctl = FC_R_CTL_DATA;
                switch ( meta->flags &
                         ( XFER_FL_CMD_STAT | XFER_FL_RESPONSE ) ) {
@@ -488,6 +502,7 @@ static int fc_xchg_tx ( struct fc_exchange *xchg, struct io_buffer *iobuf,
                        r_ctl |= FC_R_CTL_UNSOL_DATA;
                        break;
                }
+               break;
        }
 
        /* Calculate exchange and sequence control */
@@ -799,6 +814,7 @@ static void fc_port_close ( struct fc_port *port, int rc ) {
        /* Shut down interfaces */
        intf_shutdown ( &port->transport, rc );
        intf_shutdown ( &port->flogi, rc );
+       intf_shutdown ( &port->ns_plogi, rc );
 
        /* Shut down any remaining exchanges */
        list_for_each_entry_safe ( xchg, tmp, &port->xchgs, list )
@@ -922,6 +938,7 @@ int fc_port_login ( struct fc_port *port, struct fc_port_id *port_id,
                    const struct fc_name *link_port_wwn, int has_fabric ) {
        struct fc_peer *peer;
        struct fc_peer *tmp;
+       int rc;
 
        /* Perform implicit logout if logged in and details differ */
        if ( fc_link_ok ( &port->link ) &&
@@ -978,6 +995,23 @@ int fc_port_login ( struct fc_port *port, struct fc_port_id *port_id,
                       fc_id_ntoa ( &port->port_id ) );
        }
 
+       /* Log in to name server, if attached to a fabric */
+       if ( has_fabric && ! ( port->flags & FC_PORT_HAS_NS ) ) {
+
+               DBGC ( port, "FCPORT %s attempting login to name server\n",
+                      port->name );
+
+               intf_restart ( &port->ns_plogi, -ECANCELED );
+               if ( ( rc = fc_els_plogi ( &port->ns_plogi, port,
+                                          &fc_gs_port_id ) ) != 0 ) {
+                       DBGC ( port, "FCPORT %s could not initiate name "
+                              "server PLOGI: %s\n",
+                              port->name, strerror ( rc ) );
+                       fc_port_logout ( port, rc );
+                       return rc;
+               }
+       }
+
        /* Record login */
        fc_link_up ( &port->link );
 
@@ -1006,6 +1040,7 @@ void fc_port_logout ( struct fc_port *port, int rc ) {
 
        /* Erase port details */
        memset ( &port->port_id, 0, sizeof ( port->port_id ) );
+       port->flags = 0;
 
        /* Record logout */
        fc_link_err ( &port->link, rc );
@@ -1032,6 +1067,27 @@ static void fc_port_flogi_done ( struct fc_port *port, int rc ) {
                fc_port_logout ( port, rc );
 }
 
+/**
+ * Handle name server PLOGI completion
+ *
+ * @v port             Fibre Channel port
+ * @v rc               Reason for completion
+ */
+static void fc_port_ns_plogi_done ( struct fc_port *port, int rc ) {
+
+       intf_restart ( &port->ns_plogi, rc );
+
+       if ( rc == 0 ) {
+               port->flags |= FC_PORT_HAS_NS;
+               DBGC ( port, "FCPORT %s logged in to name server\n",
+                      port->name );
+       } else {
+               DBGC ( port, "FCPORT %s could not log in to name server: %s\n",
+                      port->name, strerror ( rc ) );
+               /* Absence of a name server is not a fatal error */
+       }
+}
+
 /**
  * Examine Fibre Channel port link state
  *
@@ -1107,6 +1163,15 @@ static struct interface_operation fc_port_flogi_op[] = {
 static struct interface_descriptor fc_port_flogi_desc =
        INTF_DESC ( struct fc_port, flogi, fc_port_flogi_op );
 
+/** Fibre Channel port name server PLOGI interface operations */
+static struct interface_operation fc_port_ns_plogi_op[] = {
+       INTF_OP ( intf_close, struct fc_port *, fc_port_ns_plogi_done ),
+};
+
+/** Fibre Channel port name server PLOGI interface descriptor */
+static struct interface_descriptor fc_port_ns_plogi_desc =
+       INTF_DESC ( struct fc_port, ns_plogi, fc_port_ns_plogi_op );
+
 /**
  * Create Fibre Channel port
  *
@@ -1128,6 +1193,7 @@ int fc_port_open ( struct interface *transport, const struct fc_name *node_wwn,
        intf_init ( &port->transport, &fc_port_transport_desc, &port->refcnt );
        fc_link_init ( &port->link, fc_port_examine, &port->refcnt );
        intf_init ( &port->flogi, &fc_port_flogi_desc, &port->refcnt );
+       intf_init ( &port->ns_plogi, &fc_port_ns_plogi_desc, &port->refcnt );
        list_add_tail ( &port->list, &fc_ports );
        INIT_LIST_HEAD ( &port->xchgs );
        memcpy ( &port->node_wwn, node_wwn, sizeof ( port->node_wwn ) );
@@ -1162,26 +1228,6 @@ struct fc_port * fc_port_find ( const char *name ) {
        return NULL;
 }
 
-/**
- * Find Fibre Channel port by link node name
- *
- * @v link_port_wwn    Link node name
- * @ret port           Fibre Channel port, or NULL
- */
-static struct fc_port *
-fc_port_find_link_wwn ( struct fc_name *link_port_wwn ) {
-       struct fc_port *port;
-
-       list_for_each_entry ( port, &fc_ports, list ) {
-               if ( fc_link_ok ( &port->link ) &&
-                    ( memcmp ( &port->link_port_wwn, link_port_wwn,
-                               sizeof ( port->link_port_wwn ) ) == 0 ) ) {
-                       return port;
-               }
-       }
-       return NULL;
-}
-
 /******************************************************************************
  *
  * Fibre Channel peers
@@ -1339,6 +1385,30 @@ static void fc_peer_plogi_done ( struct fc_peer *peer, int rc ) {
                fc_peer_logout ( peer, rc );
 }
 
+/**
+ * Initiate PLOGI
+ *
+ * @v peer             Fibre Channel peer
+ * @v port             Fibre Channel port
+ * @v peer_port_id     Peer port ID
+ * @ret rc             Return status code
+ */
+static int fc_peer_plogi ( struct fc_peer *peer, struct fc_port *port,
+                          struct fc_port_id *peer_port_id ) {
+       int rc;
+
+       /* Try to create PLOGI ELS */
+       intf_restart ( &peer->plogi, -ECANCELED );
+       if ( ( rc = fc_els_plogi ( &peer->plogi, port, peer_port_id ) ) != 0 ) {
+               DBGC ( peer, "FCPEER %s could not initiate PLOGI: %s\n",
+                      fc_ntoa ( &peer->port_wwn ), strerror ( rc ) );
+               fc_peer_logout ( peer, rc );
+               return rc;
+       }
+
+       return 0;
+}
+
 /**
  * Examine Fibre Channel peer link state
  *
@@ -1347,7 +1417,6 @@ static void fc_peer_plogi_done ( struct fc_peer *peer, int rc ) {
 static void fc_peer_examine ( struct fc_link_state *link ) {
        struct fc_peer *peer = container_of ( link, struct fc_peer, link );
        struct fc_port *port;
-       struct fc_port_id *peer_port_id;
        int rc;
 
        /* Check to see if underlying port link has gone down */
@@ -1366,23 +1435,36 @@ static void fc_peer_examine ( struct fc_link_state *link ) {
        /* Sanity check */
        assert ( peer->port == NULL );
 
-       /* Look for a port with the peer attached via a point-to-point link */
-       port = fc_port_find_link_wwn ( &peer->port_wwn );
-       if ( ! port ) {
-               DBGC ( peer, "FCPEER %s could not find a point-to-point "
-                      "link\n", fc_ntoa ( &peer->port_wwn ) );
-               fc_peer_logout ( peer, -ENOENT );
-               return;
+       /* First, look for a port with the peer attached via a
+        * point-to-point link.
+        */
+       list_for_each_entry ( port, &fc_ports, list ) {
+               if ( fc_link_ok ( &port->link ) &&
+                    ( ! ( port->flags & FC_PORT_HAS_FABRIC ) ) &&
+                    ( memcmp ( &peer->port_wwn, &port->link_port_wwn,
+                               sizeof ( peer->port_wwn ) ) == 0 ) ) {
+                       /* Use this peer port ID, and stop looking */
+                       fc_peer_plogi ( peer, port, &port->ptp_link_port_id );
+                       return;
+               }
        }
-       peer_port_id = &port->ptp_link_port_id;
 
-       /* Try to create PLOGI ELS */
-       intf_restart ( &peer->plogi, -ECANCELED );
-       if ( ( rc = fc_els_plogi ( &peer->plogi, port, peer_port_id ) ) != 0 ) {
-               DBGC ( peer, "FCPEER %s could not initiate PLOGI: %s\n",
-                      fc_ntoa ( &peer->port_wwn ), strerror ( rc ) );
-               fc_peer_logout ( peer, rc );
-               return;
+       /* If the peer is not directly attached, try initiating a name
+        * server lookup on any suitable ports.
+        */
+       list_for_each_entry ( port, &fc_ports, list ) {
+               if ( fc_link_ok ( &port->link ) &&
+                    ( port->flags & FC_PORT_HAS_FABRIC ) &&
+                    ( port->flags & FC_PORT_HAS_NS ) ) {
+                       if ( ( rc = fc_ns_query ( peer, port,
+                                                 fc_peer_plogi ) ) != 0 ) {
+                               DBGC ( peer, "FCPEER %s could not attempt "
+                                      "name server lookup on %s: %s\n",
+                                      fc_ntoa ( &peer->port_wwn ), port->name,
+                                      strerror ( rc ) );
+                               /* Non-fatal */
+                       }
+               }
        }
 }
 
diff --git a/src/net/fcns.c b/src/net/fcns.c
new file mode 100644 (file)
index 0000000..7769e25
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * 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 <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <ipxe/interface.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/process.h>
+#include <ipxe/xfer.h>
+#include <ipxe/fc.h>
+#include <ipxe/fcns.h>
+
+/** @file
+ *
+ * Fibre Channel name server lookups
+ *
+ */
+
+/** A Fibre Channel name server query */
+struct fc_ns_query {
+       /** Reference count */
+       struct refcnt refcnt;
+       /** Fibre Channel exchange */
+       struct interface xchg;
+
+       /** Fibre Channel peer */
+       struct fc_peer *peer;
+       /** Fibre Channel port */
+       struct fc_port *port;
+
+       /** Process */
+       struct process process;
+       /** Success handler
+        *
+        * @v peer              Fibre Channel peer
+        * @v port              Fibre Channel port
+        * @v peer_port_id      Peer port ID
+        * @ret rc              Return status code
+        */
+       int ( * done ) ( struct fc_peer *peer, struct fc_port *port,
+                        struct fc_port_id *peer_port_id );
+};
+
+/**
+ * Free name server query
+ *
+ * @v refcnt           Reference count
+ */
+static void fc_ns_query_free ( struct refcnt *refcnt ) {
+       struct fc_ns_query *query =
+               container_of ( refcnt, struct fc_ns_query, refcnt );
+
+       fc_peer_put ( query->peer );
+       fc_port_put ( query->port );
+       free ( query );
+}
+
+/**
+ * Close name server query
+ *
+ * @v query            Name server query
+ * @v rc               Reason for close
+ */
+static void fc_ns_query_close ( struct fc_ns_query *query, int rc ) {
+
+       /* Stop process */
+       process_del ( &query->process );
+
+       /* Shut down interfaces */
+       intf_shutdown ( &query->xchg, rc );
+}
+
+/**
+ * Receive name server query response
+ *
+ * @v query            Name server query
+ * @v iobuf            I/O buffer
+ * @v meta             Data transfer metadata
+ * @ret rc             Return status code
+ */
+static int fc_ns_query_deliver ( struct fc_ns_query *query,
+                                struct io_buffer *iobuf,
+                                struct xfer_metadata *meta __unused ) {
+       union fc_ns_response *resp = iobuf->data;
+       struct fc_port_id *peer_port_id;
+       int rc;
+
+       /* Sanity check */
+       if ( iob_len ( iobuf ) < sizeof ( resp->ct ) ) {
+               DBGC ( query, "FCNS %p received underlength response (%zd "
+                      "bytes)\n", query, iob_len ( iobuf ) );
+               rc = -EINVAL;
+               goto done;
+       }
+
+       /* Handle response */
+       switch ( ntohs ( resp->ct.code ) ) {
+       case FC_GS_ACCEPT:
+               if ( iob_len ( iobuf ) < sizeof ( resp->gid_pn ) ) {
+                       DBGC ( query, "FCNS %p received underlength accept "
+                              "response (%zd bytes)\n",
+                              query, iob_len ( iobuf ) );
+                       rc = -EINVAL;
+                       goto done;
+               }
+               peer_port_id = &resp->gid_pn.port_id.port_id;
+               DBGC ( query, "FCNS %p resolved %s to %s via %s\n",
+                      query, fc_ntoa ( &query->peer->port_wwn ),
+                      fc_id_ntoa ( peer_port_id ), query->port->name );
+               if ( ( rc = query->done ( query->peer, query->port,
+                                         peer_port_id ) ) != 0 )
+                       goto done;
+               break;
+       case FC_GS_REJECT:
+               DBGC ( query, "FCNS %p rejected (reason %02x explanation "
+                      "%02x)\n", query, resp->reject.ct.reason,
+                      resp->reject.ct.explanation );
+               break;
+       default:
+               DBGC ( query, "FCNS %p received invalid response code %04x\n",
+                      query, ntohs ( resp->ct.code ) );
+               rc = -ENOTSUP;
+               goto done;
+       }
+
+       rc = 0;
+ done:
+       free_iob ( iobuf );
+       fc_ns_query_close ( query, rc );
+       return rc;
+}
+
+/**
+ * Name server query process
+ *
+ * @v process          Process
+ */
+static void fc_ns_query_step ( struct process *process ) {
+       struct fc_ns_query *query =
+               container_of ( process, struct fc_ns_query, process );
+       struct xfer_metadata meta;
+       struct fc_ns_gid_pn_request gid_pn;
+       int xchg_id;
+       int rc;
+
+       /* Stop process */
+       process_del ( &query->process );
+
+       /* Create exchange */
+       if ( ( xchg_id = fc_xchg_originate ( &query->xchg, query->port,
+                                            &fc_gs_port_id,
+                                            FC_TYPE_CT ) ) < 0 ) {
+               rc = xchg_id;
+               DBGC ( query, "FCNS %p could not create exchange: %s\n",
+                      query, strerror ( rc ) );
+               fc_ns_query_close ( query, rc );
+               return;
+       }
+
+       /* Construct query request */
+       memset ( &gid_pn, 0, sizeof ( gid_pn ) );
+       gid_pn.ct.revision = FC_CT_REVISION;
+       gid_pn.ct.type = FC_GS_TYPE_DS;
+       gid_pn.ct.subtype = FC_DS_SUBTYPE_NAME;
+       gid_pn.ct.code = htons ( FC_NS_GET ( FC_NS_PORT_NAME, FC_NS_PORT_ID ));
+       memcpy ( &gid_pn.port_wwn, &query->peer->port_wwn,
+                sizeof ( gid_pn.port_wwn ) );
+       memset ( &meta, 0, sizeof ( meta ) );
+       meta.flags = XFER_FL_OVER;
+
+       /* Send query */
+       if ( ( rc = xfer_deliver_raw_meta ( &query->xchg, &gid_pn,
+                                           sizeof ( gid_pn ), &meta ) ) != 0){
+               DBGC ( query, "FCNS %p could not deliver query: %s\n",
+                      query, strerror ( rc ) );
+               fc_ns_query_close ( query, rc );
+               return;
+       }
+}
+
+/** Name server exchange interface operations */
+static struct interface_operation fc_ns_query_xchg_op[] = {
+       INTF_OP ( xfer_deliver, struct fc_ns_query *, fc_ns_query_deliver ),
+       INTF_OP ( intf_close, struct fc_ns_query *, fc_ns_query_close ),
+};
+
+/** Name server exchange interface descriptor */
+static struct interface_descriptor fc_ns_query_xchg_desc =
+       INTF_DESC ( struct fc_ns_query, xchg, fc_ns_query_xchg_op );
+
+/**
+ * Issue Fibre Channel name server query
+ *
+ * @v peer             Fibre Channel peer
+ * @v port             Fibre Channel port
+ * @ret rc             Return status code
+ */
+int fc_ns_query ( struct fc_peer *peer, struct fc_port *port,
+                 int ( * done ) ( struct fc_peer *peer, struct fc_port *port,
+                                  struct fc_port_id *peer_port_id ) ) {
+       struct fc_ns_query *query;
+
+       /* Allocate and initialise structure */
+       query = zalloc ( sizeof ( *query ) );
+       if ( ! query )
+               return -ENOMEM;
+       ref_init ( &query->refcnt, fc_ns_query_free );
+       intf_init ( &query->xchg, &fc_ns_query_xchg_desc, &query->refcnt );
+       process_init ( &query->process, fc_ns_query_step, &query->refcnt );
+       query->peer = fc_peer_get ( peer );
+       query->port = fc_port_get ( port );
+       query->done = done;
+
+       DBGC ( query, "FCNS %p querying %s via %s\n",
+              query, fc_ntoa ( &query->peer->port_wwn ), port->name );
+
+       /* Mortalise self and return */
+       ref_put ( &query->refcnt );
+       return 0;
+}