/* * Copyright (C) 2010 Michael Brown . * * 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. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include #include #include #include #include #include #include #include #include #include /** @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, };