diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index d78f2822..d530d902 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -184,6 +184,8 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define ERRFILE_wpa_tkip ( ERRFILE_NET | 0x00280000 ) #define ERRFILE_wpa_ccmp ( ERRFILE_NET | 0x00290000 ) #define ERRFILE_eth_slow ( ERRFILE_NET | 0x002a0000 ) +#define ERRFILE_fc ( ERRFILE_NET | 0x002b0000 ) +#define ERRFILE_fcels ( ERRFILE_NET | 0x002c0000 ) #define ERRFILE_image ( ERRFILE_IMAGE | 0x00000000 ) #define ERRFILE_elf ( ERRFILE_IMAGE | 0x00010000 ) diff --git a/src/include/ipxe/fc.h b/src/include/ipxe/fc.h new file mode 100644 index 00000000..5d73068f --- /dev/null +++ b/src/include/ipxe/fc.h @@ -0,0 +1,439 @@ +#ifndef _IPXE_FC_H +#define _IPXE_FC_H + +/** + * @file + * + * Fibre Channel + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + * + * Fibre Channel Names and identifiers + * + ****************************************************************************** + */ + +/** A Fibre Channel name */ +struct fc_name { + uint8_t bytes[8]; +} __attribute__ (( packed )); + +/** Length of Fibre Channel name text */ +#define FC_NAME_STRLEN 23 /* "xx:xx:xx:xx:xx:xx:xx:xx" */ + +/** A Fibre Channel port identifier */ +struct fc_port_id { + uint8_t bytes[3]; +} __attribute__ (( packed )); + +/** Length of Fibre Channel port identifier next */ +#define FC_PORT_ID_STRLEN 9 /* "xx.xx.xx" */ + +extern struct fc_port_id fc_empty_port_id; +extern struct fc_port_id fc_f_port_id; +extern struct fc_port_id fc_ptp_low_port_id; +extern struct fc_port_id fc_ptp_high_port_id; + +extern const char * fc_id_ntoa ( const struct fc_port_id *id ); +extern int fc_id_aton ( const char *id_text, struct fc_port_id *id ); +extern const char * fc_ntoa ( const struct fc_name *wwn ); +extern int fc_aton ( const char *wwn_text, struct fc_name *wwn ); + +/****************************************************************************** + * + * Fibre Channel link state + * + ****************************************************************************** + */ + +/** Delay between failed link-up attempts */ +#define FC_LINK_RETRY_DELAY ( 2 * TICKS_PER_SEC ) + +/** A Fibre Channel link state nonitor */ +struct fc_link_state { + /** Retry timer */ + struct retry_timer timer; + /** Link state */ + int rc; + /** Examine link state + * + * @v link Fibre Channel link state monitor + */ + void ( * examine ) ( struct fc_link_state *link ); +}; + +/** + * Check Fibre Channel link state + * + * @v link Fibre Channel link state monitor + * @ret link_up Link is up + */ +static inline __attribute__ (( always_inline )) int +fc_link_ok ( struct fc_link_state *link ) { + return ( link->rc == 0 ); +} + +/****************************************************************************** + * + * Fibre Channel packet formats and exchanges + * + ****************************************************************************** + */ + +/** A Fibre Channel Frame Header */ +struct fc_frame_header { + /** Routing control + * + * This is the bitwise OR of one @c fc_r_ctl_routing value and + * one @c fc_r_ctl_info value. + */ + uint8_t r_ctl; + /** Destination ID */ + struct fc_port_id d_id; + /** Class-specific control / Priority */ + uint8_t cs_ctl_prio; + /** Source ID */ + struct fc_port_id s_id; + /** Data structure type */ + uint8_t type; + /** Frame control - exchange and sequence */ + uint8_t f_ctl_es; + /** Frame control - acknowledgements */ + uint8_t f_ctl_ack; + /** Frame control - miscellaneous */ + uint8_t f_ctl_misc; + /** Sequence ID */ + uint8_t seq_id; + /** Data field control */ + uint8_t df_ctl; + /** Sequence count */ + uint16_t seq_cnt; + /** Originator exchange ID */ + uint16_t ox_id; + /** Responder exchange ID */ + uint16_t rx_id; + /** Parameter + * + * Contains the relative offset when @c FC_F_CTL_MISC_REL_OFF + * is set. + */ + uint32_t parameter; +} __attribute__ (( packed )); + +/** Fibre Channel Routing Control Routing */ +enum fc_r_ctl_routing { + FC_R_CTL_DATA = 0x00, /**< Device Data */ + FC_R_CTL_ELS = 0x20, /**< Extended Link Services */ + FC_R_CTL_FC4_LINK = 0x30, /**< FC-4 Link Data */ + FC_R_CTL_VIDEO = 0x40, /**< Video Data */ + FC_R_CTL_EH = 0x50, /**< Extended Headers */ + FC_R_CTL_BLS = 0x80, /**< Basic Link Services */ + FC_R_CTL_LINK_CTRL = 0xc0, /**< Link Control */ + FC_R_CTL_EXT_ROUTE = 0xf0, /**< Extended Routing */ +}; + +/** Fibre Channel Routing Control Routing mask */ +#define FC_R_CTL_ROUTING_MASK 0xf0 + +/** Fibre Channel Routing Control Information */ +enum fc_r_ctl_info { + FC_R_CTL_UNCAT = 0x00, /**< Uncategorized */ + FC_R_CTL_SOL_DATA = 0x01, /**< Solicited Data */ + FC_R_CTL_UNSOL_CTRL = 0x02, /**< Unsolicited Control */ + FC_R_CTL_SOL_CTRL = 0x03, /**< Solicited Control */ + FC_R_CTL_UNSOL_DATA = 0x04, /**< Unsolicited Data */ + FC_R_CTL_DATA_DESC = 0x05, /**< Data Descriptor */ + FC_R_CTL_UNSOL_CMD = 0x06, /**< Unsolicited Command */ + FC_R_CTL_CMD_STAT = 0x07, /**< Command Status */ +}; + +/** Fibre Channel Routing Control Information mask */ +#define FC_R_CTL_INFO_MASK 0x07 + +/** Fibre Channel Data Structure Type */ +enum fc_type { + FC_TYPE_BLS = 0x00, /**< Basic Link Service */ + FC_TYPE_ELS = 0x01, /**< Extended Link Service */ + FC_TYPE_FCP = 0x08, /**< Fibre Channel Protocol */ +}; + +/** Fibre Channel Frame Control - Exchange and Sequence */ +enum fc_f_ctl_es { + FC_F_CTL_ES_RESPONDER = 0x80, /**< Responder of Exchange */ + FC_F_CTL_ES_RECIPIENT = 0x40, /**< Sequence Recipient */ + FC_F_CTL_ES_FIRST = 0x20, /**< First Sequence of Exchange */ + FC_F_CTL_ES_LAST = 0x10, /**< Last Sequence of Exchange */ + FC_F_CTL_ES_END = 0x08, /**< Last Data Frame of Sequence */ + FC_F_CTL_ES_TRANSFER = 0x01, /**< Transfer Sequence Initiative */ +}; + +/** Fibre Channel Frame Control - Miscellaneous */ +enum fc_f_ctl_misc { + FC_F_CTL_MISC_REL_OFF = 0x08, /**< Relative Offset Present */ +}; + +/** Responder exchange identifier used before first response */ +#define FC_RX_ID_UNKNOWN 0xffff + +struct fc_port; + +extern int fc_xchg_originate ( struct interface *parent, struct fc_port *port, + struct fc_port_id *peer_port_id, + unsigned int type ); + +/** A Fibre Channel responder */ +struct fc_responder { + /** Type */ + unsigned int type; + /** Respond to exchange + * + * @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 + */ + int ( * respond ) ( struct interface *xchg, struct fc_port *port, + struct fc_port_id *port_id, + struct fc_port_id *peer_port_id ); +}; + +/** Fibre Channel responder table */ +#define FC_RESPONDERS __table ( struct fc_responder, "fc_responders" ) + +/** Declare a Fibre Channel responder */ +#define __fc_responder __table_entry ( FC_RESPONDERS, 01 ) + +/****************************************************************************** + * + * Fibre Channel ports + * + ****************************************************************************** + */ + +/** A Fibre Channel port */ +struct fc_port { + /** Reference count */ + struct refcnt refcnt; + /** List of all ports */ + struct list_head list; + /** Name of this port */ + char name[8]; + + /** Transport interface */ + struct interface transport; + /** Node name */ + struct fc_name node_wwn; + /** Port name */ + struct fc_name port_wwn; + /** Local port ID */ + struct fc_port_id port_id; + /** Flags */ + unsigned int flags; + + /** Link state monitor */ + struct fc_link_state link; + /** FLOGI interface */ + struct interface flogi; + /** Link node name */ + struct fc_name link_node_wwn; + /** Link port name */ + struct fc_name link_port_wwn; + /** Link port ID (for point-to-point links only) */ + struct fc_port_id ptp_link_port_id; + + /** List of active exchanges */ + struct list_head xchgs; +}; + +/** Fibre Channel port flags */ +enum fc_port_flags { + /** Port is attached to a fabric */ + FC_PORT_HAS_FABRIC = 0x0001, +}; + +/** + * Get reference to Fibre Channel port + * + * @v port Fibre Channel port + * @ret port Fibre Channel port + */ +static inline __attribute__ (( always_inline )) struct fc_port * +fc_port_get ( struct fc_port *port ) { + ref_get ( &port->refcnt ); + return port; +} + +/** + * Drop reference to Fibre Channel port + * + * @v port Fibre Channel port + */ +static inline __attribute__ (( always_inline )) void +fc_port_put ( struct fc_port *port ) { + ref_put ( &port->refcnt ); +} + +extern struct list_head fc_ports; + +extern int fc_port_login ( struct fc_port *port, struct fc_port_id *port_id, + const struct fc_name *link_node_wwn, + const struct fc_name *link_port_wwn, + int has_fabric ); +extern void fc_port_logout ( struct fc_port *port, int rc ); +extern int fc_port_open ( struct interface *transport, + const struct fc_name *node_wwn, + const struct fc_name *port_wwn ); +extern struct fc_port * fc_port_find ( const char *name ); + +/****************************************************************************** + * + * Fibre Channel peers + * + ****************************************************************************** + */ + +/** A Fibre Channel peer */ +struct fc_peer { + /** Reference count */ + struct refcnt refcnt; + /** List of all peers */ + struct list_head list; + + /** Node name */ + struct fc_name node_wwn; + + /** Link state monitor */ + struct fc_link_state link; + /** PLOGI interface */ + struct interface plogi; + /** Fibre Channel port, if known */ + struct fc_port *port; + /** Peer port ID, if known */ + struct fc_port_id port_id; + + /** List of upper-layer protocols */ + struct list_head ulps; + /** Active usage count */ + unsigned int usage; +}; + +/** + * Get reference to Fibre Channel peer + * + * @v peer Fibre Channel peer + * @ret peer Fibre Channel peer + */ +static inline __attribute__ (( always_inline )) struct fc_peer * +fc_peer_get ( struct fc_peer *peer ) { + ref_get ( &peer->refcnt ); + return peer; +} + +/** + * Drop reference to Fibre Channel peer + * + * @v peer Fibre Channel peer + */ +static inline __attribute__ (( always_inline )) void +fc_peer_put ( struct fc_peer *peer ) { + ref_put ( &peer->refcnt ); +} + +extern struct list_head fc_peers; + +extern struct fc_peer * fc_peer_get_wwn ( const struct fc_name *node_wwn ); +extern struct fc_peer * +fc_peer_get_port_id ( struct fc_port *port, + const struct fc_port_id *peer_port_id ); +extern int fc_peer_login ( struct fc_peer *peer, + struct fc_port *port, + struct fc_port_id *port_id ); +extern void fc_peer_logout ( struct fc_peer *peer, int rc ); + +/****************************************************************************** + * + * Fibre Channel upper-layer protocols + * + ****************************************************************************** + */ + +/** A Fibre Channel upper-layer protocol */ +struct fc_ulp { + /** Reference count */ + struct refcnt refcnt; + /** Fibre Channel peer */ + struct fc_peer *peer; + /** List of upper-layer protocols */ + struct list_head list; + + /** Type */ + unsigned int type; + /** Flags */ + unsigned int flags; + + /** Link state monitor */ + struct fc_link_state link; + /** PRLI interface */ + struct interface prli; + /** Service parameters, if any */ + void *param; + /** Service parameter length */ + size_t param_len; + + /** Active usage count */ + unsigned int usage; +}; + +/** Fibre Channel upper-layer protocol flags */ +enum fc_ulp_flags { + /** A login originated by us has succeeded */ + FC_ULP_ORIGINATED_LOGIN_OK = 0x0001, +}; + +/** + * Get reference to Fibre Channel upper-layer protocol + * + * @v ulp Fibre Channel upper-layer protocol + * @ret ulp Fibre Channel upper-layer protocol + */ +static inline __attribute__ (( always_inline )) struct fc_ulp * +fc_ulp_get ( struct fc_ulp *ulp ) { + ref_get ( &ulp->refcnt ); + return ulp; +} + +/** + * Drop reference to Fibre Channel upper-layer protocol + * + * @v ulp Fibre Channel upper-layer protocol + */ +static inline __attribute__ (( always_inline )) void +fc_ulp_put ( struct fc_ulp *ulp ) { + ref_put ( &ulp->refcnt ); +} + +extern struct fc_ulp * fc_ulp_get_wwn_type ( const struct fc_name *node_wwn, + unsigned int type ); +extern struct fc_ulp * +fc_ulp_get_port_id_type ( struct fc_port *port, + const struct fc_port_id *peer_port_id, + unsigned int type ); +extern void fc_ulp_increment ( struct fc_ulp *ulp ); +extern void fc_ulp_decrement ( struct fc_ulp *ulp ); +extern int fc_ulp_login ( struct fc_ulp *ulp, const void *param, + size_t param_len, int originated ); +extern void fc_ulp_logout ( struct fc_ulp *ulp, int rc ); + +#endif /* _IPXE_FC_H */ diff --git a/src/include/ipxe/fcels.h b/src/include/ipxe/fcels.h new file mode 100644 index 00000000..b35cb04c --- /dev/null +++ b/src/include/ipxe/fcels.h @@ -0,0 +1,452 @@ +#ifndef _IPXE_FCELS_H +#define _IPXE_FCELS_H + +/** + * @file + * + * Fibre Channel Extended Link Services + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include +#include +#include +#include +#include +#include + +/** Fibre Channel ELS frame common parameters */ +struct fc_els_frame_common { + /** ELS command code */ + uint8_t command; + /** Reserved */ + uint8_t reserved[3]; +} __attribute__ (( packed )); + +/** Fibre Channel ELS command codes */ +enum fc_els_command_code { + FC_ELS_LS_RJT = 0x01, /**< Link Service Reject */ + FC_ELS_LS_ACC = 0x02, /**< Link Service Accept */ + FC_ELS_PLOGI = 0x03, /**< Port Login */ + FC_ELS_FLOGI = 0x04, /**< Fabric Login */ + FC_ELS_LOGO = 0x05, /**< Logout */ + FC_ELS_RTV = 0x0e, /**< Read Timeout Value */ + FC_ELS_PRLI = 0x20, /**< Process Login */ + FC_ELS_PRLO = 0x21, /**< Process Logout */ +}; + +/** A Fibre Channel LS_RJT frame */ +struct fc_ls_rjt_frame { + /** ELS command code */ + uint8_t command; + /** Reserved */ + uint8_t reserved[4]; + /** Reason code */ + uint8_t reason; + /** Reason code explanation */ + uint8_t explanation; + /** Vendor unique */ + uint8_t vendor; +} __attribute__ (( packed )); + +/** Fibre Channel ELS rejection reason codes */ +enum fc_els_reject_reason { + /** Invalid ELS command code */ + FC_ELS_RJT_INVALID_COMMAND = 0x01, + /** Logical error */ + FC_ELS_RJT_ILLOGICAL = 0x03, + /** Logical busy */ + FC_ELS_RJT_BUSY = 0x05, + /** Protocol error */ + FC_ELS_RJT_PROTOCOL = 0x07, + /** Unable to perform command request */ + FC_ELS_RJT_UNABLE = 0x09, + /** Command not supported */ + FC_ELS_RJT_UNSUPPORTED = 0x0b, + /** Command already in progress */ + FC_ELS_RJT_IN_PROGRESS = 0x0e, +}; + +/** Fibre Channel "common" service parameters */ +struct fc_login_common { + /** Login version */ + uint16_t version; + /** Buffer-to-buffer credit */ + uint16_t credit; + /** Flags */ + uint16_t flags; + /** Receive size */ + uint16_t mtu; + /** "Common"?! */ + union { + struct { + /** Maximum number of concurrent sequences */ + uint16_t max_seq; + /** Relative offset by info category */ + uint16_t rel_offs; + } plogi; + struct { + /** Resource allocation timeout value */ + uint32_t r_a_tov; + } flogi; + } u; + /** Error detection timeout value */ + uint32_t e_d_tov; +} __attribute__ (( packed )); + +/** Fibre Channel default login version */ +#define FC_LOGIN_VERSION 0x2020 + +/** Fibre Channel default buffer-to-buffer credit */ +#define FC_LOGIN_DEFAULT_B2B 10 + +/** Continuously increasing relative offset */ +#define FC_LOGIN_CONTINUOUS_OFFSET 0x8000 + +/** Clean address */ +#define FC_LOGIN_CLEAN 0x8000 + +/** Multiple N_Port_ID support */ +#define FC_LOGIN_MULTI_N 0x8000 + +/** Random relative offset */ +#define FC_LOGIN_RANDOM_OFFSET 0x4000 + +/** Virtual fabrics */ +#define FC_LOGIN_VIRTUAL 0x4000 + +/** Vendor version level */ +#define FC_LOGIN_VENDOR 0x2000 + +/** Multiple N_Port_ID support */ +#define FC_LOGIN_MULTI_F 0x2000 + +/** Forwarder port */ +#define FC_LOGIN_F_PORT 0x1000 + +/** Alternative credit management */ +#define FC_LOGIN_ALT_CREDIT 0x0800 + +/** Name server session started */ +#define FC_LOGIN_NSS_STARTED 0x0800 + +/** Begin name server session */ +#define FC_LOGIN_NSS_BEGIN 0x0400 + +/** 1ns error detection timer resolution */ +#define FC_LOGIN_HIRES_E_D_TOV 0x0400 + +/** Broadcast supported */ +#define FC_LOGIN_BROADCAST 0x0100 + +/** Query buffer conditions */ +#define FC_LOGIN_QUERY_BUF 0x0040 + +/** Security */ +#define FC_LOGIN_SECURITY 0x0020 + +/** Clock sync primitive capable */ +#define FC_LOGIN_CLOCK_SYNC 0x0010 + +/** Short R_T timeout */ +#define FC_LOGIN_SHORT_R_T_TOV 0x0008 + +/** Dynamic half duplex */ +#define FC_LOGIN_DHD 0x0004 + +/** Continuously increasing sequence count */ +#define FC_LOGIN_CONTINUOUS_SEQ 0x0002 + +/** Payload */ +#define FC_LOGIN_PAYLOAD 0x0001 + +/** Fibre Channel default MTU */ +#define FC_LOGIN_DEFAULT_MTU 1452 + +/** Default maximum number of concurrent sequences */ +#define FC_LOGIN_DEFAULT_MAX_SEQ 255 + +/** Default relative offset by info category */ +#define FC_LOGIN_DEFAULT_REL_OFFS 0x1f + +/** Default E_D timeout value */ +#define FC_LOGIN_DEFAULT_E_D_TOV 2000 + +/** Fibre Channel class-specific login parameters */ +struct fc_login_class { + /** Flags */ + uint16_t flags; + /** Initiator flags */ + uint16_t init_flags; + /** Recipient flags */ + uint16_t recip_flags; + /** Receive data field size */ + uint16_t mtu; + /** Maximum number of concurrent sequences */ + uint16_t max_seq; + /** End-to-end credit */ + uint16_t credit; + /** Reserved */ + uint8_t reserved0; + /** Maximum number of open sequences per exchange */ + uint8_t max_seq_per_xchg; + /** Reserved */ + uint8_t reserved1[2]; +} __attribute__ (( packed )); + +/** Class valid */ +#define FC_LOGIN_CLASS_VALID 0x8000 + +/** Sequential delivery requested */ +#define FC_LOGIN_CLASS_SEQUENTIAL 0x0800 + +/** A Fibre Channel FLOGI/PLOGI frame */ +struct fc_login_frame { + /** ELS command code */ + uint8_t command; + /** Reserved */ + uint8_t reserved[3]; + /** Common service parameters */ + struct fc_login_common common; + /** Port name */ + struct fc_name port_wwn; + /** Node name */ + struct fc_name node_wwn; + /** Class 1 service parameters */ + struct fc_login_class class1; + /** Class 2 service parameters */ + struct fc_login_class class2; + /** Class 3 service parameters */ + struct fc_login_class class3; + /** Class 4 service parameters */ + struct fc_login_class class4; + /** Vendor version level */ + uint8_t vendor_version[16]; +} __attribute__ (( packed )); + +/** A Fibre Channel LOGO request frame */ +struct fc_logout_request_frame { + /** ELS command code */ + uint8_t command; + /** Reserved */ + uint8_t reserved[4]; + /** Port ID */ + struct fc_port_id port_id; + /** Port name */ + struct fc_name port_wwn; +} __attribute__ (( packed )); + +/** A Fibre Channel LOGO response frame */ +struct fc_logout_response_frame { + /** ELS command code */ + uint8_t command; + /** Reserved */ + uint8_t reserved[3]; +} __attribute__ (( packed )); + +/** A Fibre Channel PRLI service parameter page */ +struct fc_prli_page { + /** Type code */ + uint8_t type; + /** Type code extension */ + uint8_t type_ext; + /** Flags and response code */ + uint16_t flags; + /** Reserved */ + uint32_t reserved[2]; +} __attribute__ (( packed )); + +/** Establish image pair */ +#define FC_PRLI_ESTABLISH 0x2000 + +/** Response code mask */ +#define FC_PRLI_RESPONSE_MASK 0x0f00 + +/** Request was executed successfully */ +#define FC_PRLI_RESPONSE_SUCCESS 0x0100 + +/** A Fibre Channel PRLI frame */ +struct fc_prli_frame { + /** ELS command code */ + uint8_t command; + /** Page length */ + uint8_t page_len; + /** Payload length */ + uint16_t len; + /** Service parameter page */ + struct fc_prli_page page; +} __attribute__ (( packed )); + +/** A Fibre Channel RTV request frame */ +struct fc_rtv_request_frame { + /** ELS command code */ + uint8_t command; + /** Reserved */ + uint8_t reserved[3]; +} __attribute__ (( packed )); + +/** A Fibre Channel RTV response frame */ +struct fc_rtv_response_frame { + /** ELS command code */ + uint8_t command; + /** Reserved */ + uint8_t reserved0[3]; + /** Resource allocation timeout value */ + uint32_t r_a_tov; + /** Error detection timeout value */ + uint32_t e_d_tov; + /** Timeout qualifier */ + uint16_t flags; + /** Reserved */ + uint16_t reserved1; +} __attribute__ (( packed )); + +/** 1ns error detection timer resolution */ +#define FC_RTV_HIRES_E_D_TOV 0x0400 + +/** Short R_T timeout */ +#define FC_RTV_SHORT_R_T_TOV 0x0008 + +/** A Fibre Channel extended link services transaction */ +struct fc_els { + /** Reference count */ + struct refcnt refcnt; + /** Job control interface */ + struct interface job; + /** Fibre Channel exchange */ + struct interface xchg; + /** Request sending process */ + struct process process; + + /** Fibre Channel port */ + struct fc_port *port; + /** Local port ID */ + struct fc_port_id port_id; + /** Peer port ID */ + struct fc_port_id peer_port_id; + /** ELS handler, if known */ + struct fc_els_handler *handler; + /** Flags */ + unsigned int flags; +}; + +/** Fibre Channel extended link services transaction flags */ +enum fc_els_flags { + /** ELS transaction is a request */ + FC_ELS_REQUEST = 0x0001, +}; + +/** A Fibre Channel extended link services handler */ +struct fc_els_handler { + /** Name */ + const char *name; + /** Transmit ELS request frame + * + * @v els Fibre Channel ELS transaction + * @ret rc Return status code + */ + int ( * tx_request ) ( struct fc_els *els ); + /** Transmit ELS response frame + * + * @v els Fibre Channel ELS transaction + * @ret rc Return status code + */ + int ( * tx_response ) ( struct fc_els *els ); + /** Receive ELS request frame + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ + int ( * rx_request ) ( struct fc_els *els, const void *data, + size_t len ); + /** Receive ELS response frame + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ + int ( * rx_response ) ( struct fc_els *els, const void *data, + size_t len ); + /** Detect ELS request frame + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ + int ( * detect ) ( struct fc_els *els, const void *data, size_t len ); +}; + +/** Fibre Channel ELS handler table */ +#define FC_ELS_HANDLERS __table ( struct fc_els_handler, "fc_els_handlers" ) + +/** Declare a Fibre Channel ELS handler */ +#define __fc_els_handler __table_entry ( FC_ELS_HANDLERS, 01 ) + +/** A Fibre Channel ELS PRLI descriptor */ +struct fc_els_prli_descriptor { + /** Upper-layer protocol type */ + unsigned int type; + /** Service parameter length */ + size_t param_len; + /** Fibre Channel ELS handler */ + struct fc_els_handler *handler; +}; + +/** Fibre Channel ELS PRLI descriptor table */ +#define FC_ELS_PRLI_DESCRIPTORS \ + __table ( struct fc_els_prli_descriptor, "fc_els_prli_descriptors" ) + +/** Declare a Fibre Channel ELS PRLI descriptor */ +#define __fc_els_prli_descriptor __table_entry ( FC_ELS_PRLI_DESCRIPTORS, 01 ) + +/** + * Check if Fibre Channel ELS transaction is a request + * + * @v els Fibre Channel ELS transaction + * @ret is_request ELS transaction is a request + */ +static inline int fc_els_is_request ( struct fc_els *els ) { + return ( els->flags & FC_ELS_REQUEST ); +} + +/** + * Calculate ELS command to transmit + * + * @v els Fibre Channel ELS transaction + * @v request_command Command for requests + * @v command Command to transmit + */ +static inline unsigned int fc_els_tx_command ( struct fc_els *els, + unsigned int request_command ) { + return ( fc_els_is_request ( els ) ? request_command : FC_ELS_LS_ACC ); +} + +extern int fc_els_tx ( struct fc_els *els, const void *data, size_t len ); +extern int fc_els_request ( struct interface *job, struct fc_port *port, + struct fc_port_id *peer_port_id, + struct fc_els_handler *handler ); +extern int fc_els_flogi ( struct interface *parent, struct fc_port *port ); +extern int fc_els_plogi ( struct interface *parent, struct fc_port *port, + struct fc_port_id *peer_port_id ); +extern int fc_els_logo ( struct interface *parent, struct fc_port *port, + struct fc_port_id *peer_port_id ); +extern int fc_els_prli ( struct interface *parent, struct fc_port *port, + struct fc_port_id *peer_port_id, unsigned int type ); +extern int fc_els_prli_tx ( struct fc_els *els, + struct fc_els_prli_descriptor *descriptor, + void *param ); +extern int fc_els_prli_rx ( struct fc_els *els, + struct fc_els_prli_descriptor *descriptor, + const void *data, size_t len ); +extern int fc_els_prli_detect ( struct fc_els *els __unused, + struct fc_els_prli_descriptor *descriptor, + const void *data, size_t len ); + +#endif /* _IPXE_FCELS_H */ diff --git a/src/include/ipxe/xfer.h b/src/include/ipxe/xfer.h index 0c030327..1167e5cb 100644 --- a/src/include/ipxe/xfer.h +++ b/src/include/ipxe/xfer.h @@ -46,6 +46,22 @@ struct xfer_metadata { /** Offset is absolute */ #define XFER_FL_ABS_OFFSET 0x0001 +/** Sender is relinquishing use of half-duplex channel */ +#define XFER_FL_OVER 0x0002 + +/** This is the final data transfer */ +#define XFER_FL_OUT 0x0004 + +/** Data content represents a command or status message + * + * The flag @c XFER_FL_RESPONSE is used to distinguish between a + * command message and a status message. + */ +#define XFER_FL_CMD_STAT 0x0008 + +/** Data content is a response */ +#define XFER_FL_RESPONSE 0x0010 + /* Data transfer interface operations */ extern int xfer_vredirect ( struct interface *intf, int type, diff --git a/src/net/fc.c b/src/net/fc.c new file mode 100644 index 00000000..1b55a843 --- /dev/null +++ b/src/net/fc.c @@ -0,0 +1,1773 @@ +/* + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @file + * + * Fibre Channel + * + */ + +/** List of Fibre Channel ports */ +LIST_HEAD ( fc_ports ); + +/** List of Fibre Channel peers */ +LIST_HEAD ( fc_peers ); + +/****************************************************************************** + * + * Well-known addresses + * + ****************************************************************************** + */ + +/** Unassigned port ID */ +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 } }; + +/** Point-to-point low port ID */ +struct fc_port_id fc_ptp_low_port_id = { .bytes = { 0x01, 0x01, 0x01 } }; + +/** Point-to-point high port ID */ +struct fc_port_id fc_ptp_high_port_id = { .bytes = { 0x01, 0x01, 0x02 } }; + +/****************************************************************************** + * + * Utility functions + * + ****************************************************************************** + */ + +/** + * Format Fibre Channel port ID + * + * @v id Fibre Channel port ID + * @ret id_text Port ID text + */ +const char * fc_id_ntoa ( const struct fc_port_id *id ) { + static char id_text[ FC_PORT_ID_STRLEN + 1 /* NUL */ ]; + + snprintf ( id_text, sizeof ( id_text ), "%02x.%02x.%02x", + id->bytes[0], id->bytes[1], id->bytes[2] ); + return id_text; +} + +/** + * Parse Fibre Channel port ID + * + * @v id_text Port ID text + * @ret id Fibre Channel port ID + * @ret rc Return status code + */ +int fc_id_aton ( const char *id_text, struct fc_port_id *id ) { + char *ptr = ( ( char * ) id_text ); + unsigned int i = 0; + + while ( 1 ) { + id->bytes[i++] = strtoul ( ptr, &ptr, 16 ); + if ( i == sizeof ( id->bytes ) ) + return ( ( *ptr == '\0' ) ? 0 : -EINVAL ); + if ( *ptr != '.' ) + return -EINVAL; + ptr++; + } +} + +/** + * Format Fibre Channel WWN + * + * @v wwn Fibre Channel WWN + * @ret wwn_text WWN text + */ +const char * fc_ntoa ( const struct fc_name *wwn ) { + static char wwn_text[ FC_NAME_STRLEN + 1 /* NUL */ ]; + + snprintf ( wwn_text, sizeof ( wwn_text ), + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", + wwn->bytes[0], wwn->bytes[1], wwn->bytes[2], wwn->bytes[3], + wwn->bytes[4], wwn->bytes[5], wwn->bytes[6], wwn->bytes[7] ); + return wwn_text; +} + +/** + * Parse Fibre Channel WWN + * + * @v wwn_text WWN text + * @ret wwn Fibre Channel WWN + * @ret rc Return status code + */ +int fc_aton ( const char *wwn_text, struct fc_name *wwn ) { + char *ptr = ( ( char * ) wwn_text ); + unsigned int i = 0; + + while ( 1 ) { + wwn->bytes[i++] = strtoul ( ptr, &ptr, 16 ); + if ( i == sizeof ( wwn->bytes ) ) + return ( ( *ptr == '\0' ) ? 0 : -EINVAL ); + if ( *ptr != ':' ) + return -EINVAL; + ptr++; + } +} + +/****************************************************************************** + * + * Fibre Channel link state + * + ****************************************************************************** + */ + +/** Default link status code */ +#define EUNKNOWN_LINK_STATUS __einfo_error ( EINFO_EUNKNOWN_LINK_STATUS ) +#define EINFO_EUNKNOWN_LINK_STATUS \ + __einfo_uniqify ( EINFO_EINPROGRESS, 0x01, "Unknown" ) + +/** + * Mark Fibre Channel link as up + * + * @v link Fibre Channel link state monitor + */ +static void fc_link_up ( struct fc_link_state *link ) { + + /* Stop retry timer */ + stop_timer ( &link->timer ); + + /* Record link state */ + link->rc = 0; +} + +/** + * Mark Fibre Channel link as down + * + * @v link Fibre Channel link state monitor + * @v rc Link state + */ +static void fc_link_err ( struct fc_link_state *link, int rc ) { + + /* Record link state */ + if ( rc == 0 ) + rc = -EUNKNOWN_LINK_STATUS; + link->rc = rc; + + /* Schedule another link examination */ + start_timer_fixed ( &link->timer, FC_LINK_RETRY_DELAY ); +} + +/** + * Examine Fibre Channel link state + * + * @v link Fibre Channel link state monitor + */ +static void fc_link_examine ( struct fc_link_state *link ) { + + link->examine ( link ); +} + +/** + * Handle Fibre Channel link retry timer expiry + */ +static void fc_link_expired ( struct retry_timer *timer, int over __unused ) { + struct fc_link_state *link = + container_of ( timer, struct fc_link_state, timer ); + + /* Schedule another link examination */ + start_timer_fixed ( &link->timer, FC_LINK_RETRY_DELAY ); + + /* Examine link */ + fc_link_examine ( link ); +} + +/** + * Initialise Fibre Channel link state monitor + * + * @v link Fibre Channel link state monitor + * @v examine Examine link state method + * @v refcnt Reference counter + */ +static void fc_link_init ( struct fc_link_state *link, + void ( * examine ) ( struct fc_link_state *link ), + struct refcnt *refcnt ) { + + link->rc = -EUNKNOWN_LINK_STATUS; + timer_init ( &link->timer, fc_link_expired, refcnt ); + link->examine = examine; +} + +/** + * Start monitoring Fibre Channel link state + * + * @v link Fibre Channel link state monitor + */ +static void fc_link_start ( struct fc_link_state *link ) { + start_timer_nodelay ( &link->timer ); +} + +/** + * Stop monitoring Fibre Channel link state + * + * @v link Fibre Channel link state monitor + */ +static void fc_link_stop ( struct fc_link_state *link ) { + stop_timer ( &link->timer ); +} + +/****************************************************************************** + * + * Fibre Channel exchanges + * + ****************************************************************************** + */ + +/** A Fibre Channel exchange */ +struct fc_exchange { + /** Reference count */ + struct refcnt refcnt; + /** Fibre Channel port */ + struct fc_port *port; + /** List of active exchanges within this port */ + struct list_head list; + + /** Peer port ID */ + struct fc_port_id peer_port_id; + /** Data structure type */ + unsigned int type; + /** Flags */ + unsigned int flags; + /** Local exchange ID */ + uint16_t xchg_id; + /** Peer exchange ID */ + uint16_t peer_xchg_id; + /** Active sequence ID */ + uint8_t seq_id; + /** Active sequence count */ + uint16_t seq_cnt; + + /** Timeout timer */ + struct retry_timer timer; + + /** Upper-layer protocol interface */ + struct interface ulp; +}; + +/** Fibre Channel exchange flags */ +enum fc_exchange_flags { + /** We are the exchange originator */ + FC_XCHG_ORIGINATOR = 0x0001, + /** We have the sequence initiative */ + FC_XCHG_SEQ_INITIATIVE = 0x0002, + /** This is the first sequence of the exchange */ + FC_XCHG_SEQ_FIRST = 0x0004, +}; + +/** Fibre Channel timeout */ +#define FC_TIMEOUT ( 1 * TICKS_PER_SEC ) + +/** + * Create local Fibre Channel exchange identifier + * + * @ret xchg_id Local exchange ID + */ +static unsigned int fc_new_xchg_id ( void ) { + static uint16_t next_id = 0x0000; + + /* We must avoid using FC_RX_ID_UNKNOWN (0xffff) */ + next_id += 2; + return next_id; +} + +/** + * Create local Fibre Channel sequence identifier + * + * @ret seq_id Local sequence identifier + */ +static unsigned int fc_new_seq_id ( void ) { + static uint8_t seq_id = 0x00; + + return (++seq_id); +} + +/** + * Free Fibre Channel exchange + * + * @v refcnt Reference count + */ +static void fc_xchg_free ( struct refcnt *refcnt ) { + struct fc_exchange *xchg = + container_of ( refcnt, struct fc_exchange, refcnt ); + + assert ( ! timer_running ( &xchg->timer ) ); + assert ( list_empty ( &xchg->list ) ); + + fc_port_put ( xchg->port ); + free ( xchg ); +} + +/** + * Close Fibre Channel exchange + * + * @v xchg Fibre Channel exchange + * @v rc Reason for close + */ +static void fc_xchg_close ( struct fc_exchange *xchg, int rc ) { + struct fc_port *port = xchg->port; + + if ( rc != 0 ) { + DBGC2 ( port, "FCXCHG %s/%04x closed: %s\n", + port->name, xchg->xchg_id, strerror ( rc ) ); + } + + /* Stop timer */ + stop_timer ( &xchg->timer ); + + /* If list still holds a reference, remove from list of open + * exchanges and drop list's reference. + */ + if ( ! list_empty ( &xchg->list ) ) { + list_del ( &xchg->list ); + INIT_LIST_HEAD ( &xchg->list ); + ref_put ( &xchg->refcnt ); + } + + /* Shutdown interfaces */ + intf_shutdown ( &xchg->ulp, rc ); +} + +/** + * Handle exchange timeout + * + * @v timer Timeout timer + * @v over Failure indicator + */ +static void fc_xchg_expired ( struct retry_timer *timer, int over __unused ) { + struct fc_exchange *xchg = + container_of ( timer, struct fc_exchange, timer ); + struct fc_port *port = xchg->port; + + DBGC ( port, "FCXCHG %s/%04x timed out\n", port->name, xchg->xchg_id ); + + /* Terminate the exchange */ + fc_xchg_close ( xchg, -ETIMEDOUT ); +} + +/** + * Check Fibre Channel exchange window + * + * @v xchg Fibre Channel exchange + * @ret len Length opf window + */ +static size_t fc_xchg_window ( struct fc_exchange *xchg __unused ) { + + /* We don't currently store the path MTU */ + return FC_LOGIN_DEFAULT_MTU; +} + +/** + * Allocate Fibre Channel I/O buffer + * + * @v xchg Fibre Channel exchange + * @v len Payload length + * @ret iobuf I/O buffer, or NULL + */ +static struct io_buffer * fc_xchg_alloc_iob ( struct fc_exchange *xchg, + size_t len ) { + struct fc_port *port = xchg->port; + struct io_buffer *iobuf; + + iobuf = xfer_alloc_iob ( &port->transport, + ( sizeof ( struct fc_frame_header ) + len ) ); + if ( iobuf ) { + iob_reserve ( iobuf, sizeof ( struct fc_frame_header ) ); + } + return iobuf; +} + +/** + * Transmit data as part of a Fibre Channel exchange + * + * @v xchg Fibre Channel exchange + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int fc_xchg_tx ( struct fc_exchange *xchg, struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + struct fc_port *port = xchg->port; + struct fc_frame_header *fchdr; + unsigned int r_ctl; + unsigned int f_ctl_es; + int rc; + + /* Sanity checks */ + if ( ! ( xchg->flags & FC_XCHG_SEQ_INITIATIVE ) ) { + DBGC ( port, "FCXCHG %s/%04x cannot transmit while not " + "holding sequence initiative\n", + port->name, xchg->xchg_id ); + rc = -EBUSY; + goto done; + } + + /* Calculate routing control */ + if ( xchg->type == 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 { + r_ctl = FC_R_CTL_DATA; + switch ( meta->flags & + ( XFER_FL_CMD_STAT | XFER_FL_RESPONSE ) ) { + case ( XFER_FL_CMD_STAT | XFER_FL_RESPONSE ): + r_ctl |= FC_R_CTL_CMD_STAT; + break; + case ( XFER_FL_CMD_STAT ): + r_ctl |= FC_R_CTL_UNSOL_CMD; + break; + case ( XFER_FL_RESPONSE ): + r_ctl |= FC_R_CTL_SOL_DATA; + break; + default: + r_ctl |= FC_R_CTL_UNSOL_DATA; + break; + } + } + + /* Calculate exchange and sequence control */ + f_ctl_es = 0; + if ( ! ( xchg->flags & FC_XCHG_ORIGINATOR ) ) + f_ctl_es |= FC_F_CTL_ES_RESPONDER; + if ( xchg->flags & FC_XCHG_SEQ_FIRST ) + f_ctl_es |= FC_F_CTL_ES_FIRST; + if ( meta->flags & XFER_FL_OUT ) + f_ctl_es |= ( FC_F_CTL_ES_END | FC_F_CTL_ES_LAST ); + if ( meta->flags & XFER_FL_OVER ) + f_ctl_es |= ( FC_F_CTL_ES_END | FC_F_CTL_ES_TRANSFER ); + + /* Create frame header */ + fchdr = iob_push ( iobuf, sizeof ( *fchdr ) ); + memset ( fchdr, 0, sizeof ( *fchdr ) ); + fchdr->r_ctl = r_ctl; + memcpy ( &fchdr->d_id, &xchg->peer_port_id, sizeof ( fchdr->d_id ) ); + memcpy ( &fchdr->s_id, &port->port_id, sizeof ( fchdr->s_id ) ); + fchdr->type = xchg->type; + fchdr->f_ctl_es = f_ctl_es; + fchdr->seq_id = xchg->seq_id; + fchdr->seq_cnt = htons ( xchg->seq_cnt++ ); + fchdr->ox_id = htons ( ( xchg->flags & FC_XCHG_ORIGINATOR ) ? + xchg->xchg_id : xchg->peer_xchg_id ); + fchdr->rx_id = htons ( ( xchg->flags & FC_XCHG_ORIGINATOR ) ? + xchg->peer_xchg_id : xchg->xchg_id ); + if ( meta->flags & XFER_FL_ABS_OFFSET ) { + fchdr->f_ctl_misc |= FC_F_CTL_MISC_REL_OFF; + fchdr->parameter = htonl ( meta->offset ); + } + + /* Relinquish sequence initiative if applicable */ + if ( meta->flags & XFER_FL_OVER ) { + xchg->flags &= ~( FC_XCHG_SEQ_INITIATIVE | FC_XCHG_SEQ_FIRST ); + xchg->seq_cnt = 0; + } + + /* Reset timeout */ + start_timer_fixed ( &xchg->timer, FC_TIMEOUT ); + + /* Deliver frame */ + if ( ( rc = xfer_deliver_iob ( &port->transport, + iob_disown ( iobuf ) ) ) != 0 ) { + DBGC ( port, "FCXCHG %s/%04x cannot transmit: %s\n", + port->name, xchg->xchg_id, strerror ( rc ) ); + goto done; + } + + done: + free_iob ( iobuf ); + return rc; +} + +/** Mapping from Fibre Channel routing control information to xfer metadata */ +static const uint8_t fc_r_ctl_info_meta_flags[ FC_R_CTL_INFO_MASK + 1 ] = { + [FC_R_CTL_UNCAT] = ( 0 ), + [FC_R_CTL_SOL_DATA] = ( XFER_FL_RESPONSE ), + [FC_R_CTL_UNSOL_CTRL] = ( XFER_FL_CMD_STAT ), + [FC_R_CTL_SOL_CTRL] = ( XFER_FL_CMD_STAT ), + [FC_R_CTL_UNSOL_DATA] = ( 0 ), + [FC_R_CTL_DATA_DESC] = ( XFER_FL_CMD_STAT ), + [FC_R_CTL_UNSOL_CMD] = ( XFER_FL_CMD_STAT ), + [FC_R_CTL_CMD_STAT] = ( XFER_FL_CMD_STAT | XFER_FL_RESPONSE ), +}; + +/** + * Receive data as part of a Fibre Channel exchange + * + * @v xchg Fibre Channel exchange + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int fc_xchg_rx ( struct fc_exchange *xchg, struct io_buffer *iobuf, + struct xfer_metadata *meta __unused ) { + struct fc_port *port = xchg->port; + struct fc_frame_header *fchdr = iobuf->data; + struct xfer_metadata fc_meta; + int rc; + + /* Record peer exchange ID */ + xchg->peer_xchg_id = + ntohs ( ( fchdr->f_ctl_es & FC_F_CTL_ES_RESPONDER ) ? + fchdr->rx_id : fchdr->ox_id ); + + /* Sequence checks */ + if ( xchg->flags & FC_XCHG_SEQ_INITIATIVE ) { + DBGC ( port, "FCXCHG %s/%04x received frame while holding " + "sequence initiative\n", port->name, xchg->xchg_id ); + rc = -EBUSY; + goto done; + } + if ( ntohs ( fchdr->seq_cnt ) != xchg->seq_cnt ) { + DBGC ( port, "FCXCHG %s/%04x received out-of-order frame %d " + "(expected %d)\n", port->name, xchg->xchg_id, + ntohs ( fchdr->seq_cnt ), xchg->seq_cnt ); + rc = -EPIPE; + goto done; + } + if ( xchg->seq_cnt == 0 ) + xchg->seq_id = fchdr->seq_id; + xchg->seq_cnt++; + if ( fchdr->seq_id != xchg->seq_id ) { + DBGC ( port, "FCXCHG %s/%04x received frame for incorrect " + "sequence %02x (expected %02x)\n", port->name, + xchg->xchg_id, fchdr->seq_id, xchg->seq_id ); + rc = -EPIPE; + goto done; + } + + /* Check for end of sequence and transfer of sequence initiative */ + if ( fchdr->f_ctl_es & FC_F_CTL_ES_END ) { + xchg->seq_cnt = 0; + if ( fchdr->f_ctl_es & FC_F_CTL_ES_TRANSFER ) { + xchg->flags |= FC_XCHG_SEQ_INITIATIVE; + xchg->seq_id = fc_new_seq_id(); + } + } + + /* Construct metadata */ + memset ( &fc_meta, 0, sizeof ( fc_meta ) ); + fc_meta.flags = + fc_r_ctl_info_meta_flags[ fchdr->r_ctl & FC_R_CTL_INFO_MASK ]; + if ( fchdr->f_ctl_es & FC_F_CTL_ES_TRANSFER ) { + fc_meta.flags |= XFER_FL_OVER; + } + if ( ( fchdr->f_ctl_es & FC_F_CTL_ES_LAST ) && + ( fchdr->f_ctl_es & FC_F_CTL_ES_END ) ) { + fc_meta.flags |= XFER_FL_OUT; + } + if ( fchdr->f_ctl_misc & FC_F_CTL_MISC_REL_OFF ) { + fc_meta.flags |= XFER_FL_ABS_OFFSET; + fc_meta.offset = ntohl ( fchdr->parameter ); + } + + /* Reset timeout */ + start_timer_fixed ( &xchg->timer, FC_TIMEOUT ); + + /* Deliver via exchange's ULP interface */ + iob_pull ( iobuf, sizeof ( *fchdr ) ); + if ( ( rc = xfer_deliver ( &xchg->ulp, iob_disown ( iobuf ), + &fc_meta ) ) != 0 ) { + DBGC ( port, "FCXCHG %s/%04x cannot deliver frame: %s\n", + port->name, xchg->xchg_id, strerror ( rc ) ); + goto done; + } + + /* Close exchange if applicable */ + if ( ( fchdr->f_ctl_es & FC_F_CTL_ES_LAST ) && + ( fchdr->f_ctl_es & FC_F_CTL_ES_END ) ) { + fc_xchg_close ( xchg, 0 ); + } + + done: + free_iob ( iobuf ); + return rc; +} + +/** Fibre Channel exchange ULP interface operations */ +static struct interface_operation fc_xchg_ulp_op[] = { + INTF_OP ( xfer_deliver, struct fc_exchange *, fc_xchg_tx ), + INTF_OP ( xfer_alloc_iob, struct fc_exchange *, fc_xchg_alloc_iob ), + INTF_OP ( xfer_window, struct fc_exchange *, fc_xchg_window ), + INTF_OP ( intf_close, struct fc_exchange *, fc_xchg_close ), +}; + +/** Fibre Channel exchange ULP interface descriptor */ +static struct interface_descriptor fc_xchg_ulp_desc = + INTF_DESC ( struct fc_exchange, ulp, fc_xchg_ulp_op ); + +/** + * Create new Fibre Channel exchange + * + * @v port Fibre Channel port + * @v peer_port_id Peer port ID + * @ret xchg Exchange, or NULL + */ +static struct fc_exchange * fc_xchg_create ( struct fc_port *port, + struct fc_port_id *peer_port_id, + unsigned int type ) { + struct fc_exchange *xchg; + + /* Allocate and initialise structure */ + xchg = zalloc ( sizeof ( *xchg ) ); + if ( ! xchg ) + return NULL; + ref_init ( &xchg->refcnt, fc_xchg_free ); + intf_init ( &xchg->ulp, &fc_xchg_ulp_desc, &xchg->refcnt ); + timer_init ( &xchg->timer, fc_xchg_expired, &xchg->refcnt ); + xchg->port = fc_port_get ( port ); + memcpy ( &xchg->peer_port_id, peer_port_id, + sizeof ( xchg->peer_port_id ) ); + xchg->type = type; + xchg->xchg_id = fc_new_xchg_id(); + xchg->peer_xchg_id = FC_RX_ID_UNKNOWN; + xchg->seq_id = fc_new_seq_id(); + + /* Transfer reference to list of exchanges and return */ + list_add ( &xchg->list, &port->xchgs ); + return xchg; +} + +/** + * Originate a new Fibre Channel exchange + * + * @v parent Interface to which to attach + * @v port Fibre Channel port + * @v peer_port_id Peer port ID + * @ret rc Return status code + */ +int fc_xchg_originate ( struct interface *parent, struct fc_port *port, + struct fc_port_id *peer_port_id, unsigned int type ) { + struct fc_exchange *xchg; + + /* Allocate and initialise structure */ + xchg = fc_xchg_create ( port, peer_port_id, type ); + if ( ! xchg ) + return -ENOMEM; + xchg->flags = ( FC_XCHG_ORIGINATOR | FC_XCHG_SEQ_INITIATIVE | + FC_XCHG_SEQ_FIRST ); + + DBGC2 ( port, "FCXCHG %s/%04x originating to %s (type %02x)\n", + port->name, xchg->xchg_id, fc_id_ntoa ( &xchg->peer_port_id ), + xchg->type ); + + /* Attach to parent interface and return */ + intf_plug_plug ( &xchg->ulp, parent ); + return 0; +} + +/** + * Open a new responder Fibre Channel exchange + * + * @v port Fibre Channel port + * @v fchdr Fibre Channel frame header + * @ret xchg Fibre Channel exchange, or NULL + */ +static struct fc_exchange * fc_xchg_respond ( struct fc_port *port, + struct fc_frame_header *fchdr ) { + struct fc_exchange *xchg; + struct fc_responder *responder; + unsigned int type = fchdr->type; + int rc; + + /* Allocate and initialise structure */ + xchg = fc_xchg_create ( port, &fchdr->s_id, type ); + if ( ! xchg ) + return NULL; + xchg->seq_id = fchdr->seq_id; + + DBGC2 ( port, "FCXCHG %s/%04x responding to %s xchg %04x (type " + "%02x)\n", port->name, xchg->xchg_id, + fc_id_ntoa ( &xchg->peer_port_id ), + ntohs ( fchdr->ox_id ), xchg->type ); + + /* Find a responder, if any */ + for_each_table_entry ( responder, FC_RESPONDERS ) { + if ( responder->type == type ) { + if ( ( rc = responder->respond ( &xchg->ulp, port, + &fchdr->d_id, + &fchdr->s_id ) ) !=0 ){ + DBGC ( port, "FCXCHG %s/%04x could not " + "respond: %s\n", port->name, + xchg->xchg_id, strerror ( rc ) ); + } + } + break; + } + + /* We may or may not have a ULP attached at this point, but + * the exchange does exist. + */ + return xchg; +} + +/****************************************************************************** + * + * Fibre Channel ports + * + ****************************************************************************** + */ + +/** + * Close Fibre Channel port + * + * @v port Fibre Channel port + * @v rc Reason for close + */ +static void fc_port_close ( struct fc_port *port, int rc ) { + struct fc_exchange *xchg; + struct fc_exchange *tmp; + + DBGC ( port, "FCPORT %s closed\n", port->name ); + + /* Log out port, if necessary */ + if ( fc_link_ok ( &port->link ) ) + fc_port_logout ( port, rc ); + + /* Stop link monitor */ + fc_link_stop ( &port->link ); + + /* Shut down interfaces */ + intf_shutdown ( &port->transport, rc ); + intf_shutdown ( &port->flogi, rc ); + + /* Shut down any remaining exchanges */ + list_for_each_entry_safe ( xchg, tmp, &port->xchgs, list ) + fc_xchg_close ( xchg, rc ); + + /* Remove from list of ports */ + list_del ( &port->list ); + INIT_LIST_HEAD ( &port->list ); +} + +/** + * Identify Fibre Channel exchange by local exchange ID + * + * @v port Fibre Channel port + * @v xchg_id Local exchange ID + * @ret xchg Fibre Channel exchange, or NULL + */ +static struct fc_exchange * fc_port_demux ( struct fc_port *port, + unsigned int xchg_id ) { + struct fc_exchange *xchg; + + list_for_each_entry ( xchg, &port->xchgs, list ) { + if ( xchg->xchg_id == xchg_id ) + return xchg; + } + return NULL; +} + +/** + * Handle received frame from Fibre Channel port + * + * @v port Fibre Channel port + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int fc_port_deliver ( struct fc_port *port, struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + struct fc_frame_header *fchdr = iobuf->data; + unsigned int xchg_id; + struct fc_exchange *xchg; + int rc; + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *fchdr ) ) { + DBGC ( port, "FCPORT %s received underlength frame (%zd " + "bytes)\n", port->name, iob_len ( iobuf ) ); + rc = -EINVAL; + goto err_sanity; + } + + /* Verify local port ID */ + if ( ( memcmp ( &fchdr->d_id, &port->port_id, + sizeof ( fchdr->d_id ) ) != 0 ) && + ( memcmp ( &fchdr->d_id, &fc_f_port_id, + sizeof ( fchdr->d_id ) ) != 0 ) && + ( memcmp ( &port->port_id, &fc_empty_port_id, + sizeof ( port->port_id ) ) != 0 ) ) { + DBGC ( port, "FCPORT %s received frame for incorrect port ID " + "%s\n", port->name, fc_id_ntoa ( &fchdr->d_id ) ); + rc = -ENOTCONN; + goto err_port_id; + } + + /* Demultiplex amongst active exchanges */ + xchg_id = ntohs ( ( fchdr->f_ctl_es & FC_F_CTL_ES_RESPONDER ) ? + fchdr->ox_id : fchdr->rx_id ); + xchg = fc_port_demux ( port, xchg_id ); + + /* If we have no active exchange and this frame starts a new + * exchange, try to create a new responder exchange + */ + if ( ( fchdr->f_ctl_es & FC_F_CTL_ES_FIRST ) && + ( fchdr->seq_cnt == 0 ) ) { + + /* Create new exchange */ + xchg = fc_xchg_respond ( port, fchdr ); + if ( ! xchg ) { + DBGC ( port, "FCPORT %s cannot create new exchange\n", + port->name ); + rc = -ENOMEM; + goto err_respond; + } + } + + /* Fail if no exchange exists */ + if ( ! xchg ) { + DBGC ( port, "FCPORT %s xchg %04x unknown\n", + port->name, xchg_id ); + rc = -ENOTCONN; + goto err_no_xchg; + } + + /* Pass received frame to exchange */ + ref_get ( &xchg->refcnt ); + if ( ( rc = fc_xchg_rx ( xchg, iob_disown ( iobuf ), meta ) ) != 0 ) + goto err_xchg_rx; + + err_xchg_rx: + ref_put ( &xchg->refcnt ); + err_no_xchg: + err_respond: + err_port_id: + err_sanity: + free_iob ( iobuf ); + return rc; +} + +/** + * Log in Fibre Channel port + * + * @v port Fibre Channel port + * @v port_id Local port ID + * @v link_node_wwn Link node name + * @v link_port_wwn Link port name + * @v has_fabric Link is to a fabric + * @ret rc Return status code + */ +int fc_port_login ( struct fc_port *port, struct fc_port_id *port_id, + const struct fc_name *link_node_wwn, + const struct fc_name *link_port_wwn, int has_fabric ) { + struct fc_peer *peer; + struct fc_peer *tmp; + + /* Perform implicit logout if logged in and details differ */ + if ( fc_link_ok ( &port->link ) && + ( ( ( !! ( port->flags & FC_PORT_HAS_FABRIC ) ) != + ( !! has_fabric ) ) || + ( memcmp ( &port->link_node_wwn, link_node_wwn, + sizeof ( port->link_node_wwn ) ) != 0 ) || + ( memcmp ( &port->link_port_wwn, link_port_wwn, + sizeof ( port->link_port_wwn ) ) != 0 ) || + ( memcmp ( &port->port_id, port_id, + sizeof ( port->port_id ) ) != 0 ) ) ) { + fc_port_logout ( port, 0 ); + } + + /* Log in, if applicable */ + if ( ! fc_link_ok ( &port->link ) ) { + + /* Record link port name */ + memcpy ( &port->link_node_wwn, link_node_wwn, + sizeof ( port->link_node_wwn ) ); + memcpy ( &port->link_port_wwn, link_port_wwn, + sizeof ( port->link_port_wwn ) ); + DBGC ( port, "FCPORT %s logged in to %s", + port->name, fc_ntoa ( &port->link_node_wwn ) ); + DBGC ( port, " port %s\n", fc_ntoa ( &port->link_port_wwn ) ); + + /* Calculate local (and possibly remote) port IDs */ + if ( has_fabric ) { + port->flags |= FC_PORT_HAS_FABRIC; + memcpy ( &port->port_id, port_id, + sizeof ( port->port_id ) ); + } else { + port->flags &= ~FC_PORT_HAS_FABRIC; + if ( memcmp ( &port->port_wwn, link_port_wwn, + sizeof ( port->port_wwn ) ) > 0 ) { + memcpy ( &port->port_id, &fc_ptp_high_port_id, + sizeof ( port->port_id ) ); + memcpy ( &port->ptp_link_port_id, + &fc_ptp_low_port_id, + sizeof ( port->ptp_link_port_id ) ); + } else { + memcpy ( &port->port_id, &fc_ptp_low_port_id, + sizeof ( port->port_id ) ); + memcpy ( &port->ptp_link_port_id, + &fc_ptp_high_port_id, + sizeof ( port->ptp_link_port_id ) ); + } + } + DBGC ( port, "FCPORT %s logged in via a %s, with local ID " + "%s\n", port->name, + ( ( port->flags & FC_PORT_HAS_FABRIC ) ? + "fabric" : "point-to-point link" ), + fc_id_ntoa ( &port->port_id ) ); + } + + /* Record login */ + fc_link_up ( &port->link ); + + /* Notify peers of link state change */ + list_for_each_entry_safe ( peer, tmp, &fc_peers, list ) + fc_link_examine ( &peer->link ); + + return 0; +} + +/** + * Log out Fibre Channel port + * + * @v port Fibre Channel port + * @v rc Reason for logout + */ +void fc_port_logout ( struct fc_port *port, int rc ) { + struct fc_peer *peer; + struct fc_peer *tmp; + + DBGC ( port, "FCPORT %s logged out: %s\n", + port->name, strerror ( rc ) ); + + /* Erase port details */ + memset ( &port->port_id, 0, sizeof ( port->port_id ) ); + + /* Record logout */ + fc_link_err ( &port->link, rc ); + + /* Notify peers of link state change */ + list_for_each_entry_safe ( peer, tmp, &fc_peers, list ) + fc_link_examine ( &peer->link ); +} + +/** + * Handle FLOGI completion + * + * @v port Fibre Channel port + * @v rc Reason for completion + */ +static void fc_port_flogi_done ( struct fc_port *port, int rc ) { + + intf_restart ( &port->flogi, rc ); + + if ( rc != 0 ) + fc_port_logout ( port, rc ); +} + +/** + * Examine Fibre Channel port link state + * + * @ link Fibre Channel link state monitor + */ +static void fc_port_examine ( struct fc_link_state *link ) { + struct fc_port *port = container_of ( link, struct fc_port, link ); + int rc; + + /* Do nothing if already logged in */ + if ( fc_link_ok ( &port->link ) ) + return; + + DBGC ( port, "FCPORT %s attempting login\n", port->name ); + + /* Try to create FLOGI ELS */ + intf_restart ( &port->flogi, -ECANCELED ); + if ( ( rc = fc_els_flogi ( &port->flogi, port ) ) != 0 ) { + DBGC ( port, "FCPORT %s could not initiate FLOGI: %s\n", + port->name, strerror ( rc ) ); + fc_port_logout ( port, rc ); + return; + } +} + +/** + * Handle change of flow control window + * + * @v port Fibre Channel port + */ +static void fc_port_window_changed ( struct fc_port *port ) { + size_t window; + + /* Check if transport layer is ready */ + window = xfer_window ( &port->transport ); + if ( window > 0 ) { + + /* Transport layer is ready. Start login if the link + * is not already up. + */ + if ( ! fc_link_ok ( &port->link ) ) + fc_link_start ( &port->link ); + + } else { + + /* Transport layer is not ready. Log out port and + * wait for transport layer before attempting log in + * again. + */ + fc_port_logout ( port, -ENOTCONN ); + fc_link_stop ( &port->link ); + } +} + +/** Fibre Channel port transport interface operations */ +static struct interface_operation fc_port_transport_op[] = { + INTF_OP ( xfer_deliver, struct fc_port *, fc_port_deliver ), + INTF_OP ( xfer_window_changed, struct fc_port *, + fc_port_window_changed ), + INTF_OP ( intf_close, struct fc_port *, fc_port_close ), +}; + +/** Fibre Channel port transport interface descriptor */ +static struct interface_descriptor fc_port_transport_desc = + INTF_DESC ( struct fc_port, transport, fc_port_transport_op ); + +/** Fibre Channel port FLOGI interface operations */ +static struct interface_operation fc_port_flogi_op[] = { + INTF_OP ( intf_close, struct fc_port *, fc_port_flogi_done ), +}; + +/** Fibre Channel port FLOGI interface descriptor */ +static struct interface_descriptor fc_port_flogi_desc = + INTF_DESC ( struct fc_port, flogi, fc_port_flogi_op ); + +/** + * Create Fibre Channel port + * + * @v transport Transport interface + * @v node Fibre Channel node name + * @v port Fibre Channel port name + * @ret rc Return status code + */ +int fc_port_open ( struct interface *transport, const struct fc_name *node_wwn, + const struct fc_name *port_wwn ) { + static unsigned int portindex = 0; + struct fc_port *port; + + /* Allocate and initialise structure */ + port = zalloc ( sizeof ( *port ) ); + if ( ! port ) + return -ENOMEM; + ref_init ( &port->refcnt, NULL ); + 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 ); + list_add ( &port->list, &fc_ports ); + INIT_LIST_HEAD ( &port->xchgs ); + memcpy ( &port->node_wwn, node_wwn, sizeof ( port->node_wwn ) ); + memcpy ( &port->port_wwn, port_wwn, sizeof ( port->port_wwn ) ); + + /* Create device name */ + snprintf ( port->name, sizeof ( port->name ), "fc%d", portindex++ ); + + DBGC ( port, "FCPORT %s opened as %s", + port->name, fc_ntoa ( &port->node_wwn ) ); + DBGC ( port, " port %s\n", fc_ntoa ( &port->port_wwn ) ); + + /* Attach to transport layer, mortalise self, and return */ + intf_plug_plug ( &port->transport, transport ); + ref_put ( &port->refcnt ); + return 0; +} + +/** + * Find Fibre Channel port by name + * + * @v name Fibre Channel port name + * @ret port Fibre Channel port, or NULL + */ +struct fc_port * fc_port_find ( const char *name ) { + struct fc_port *port; + + list_for_each_entry ( port, &fc_ports, list ) { + if ( strcmp ( name, port->name ) == 0 ) + return port; + } + return NULL; +} + +/** + * Find Fibre Channel port by link node name + * + * @v link_node_wwn Link node name + * @ret port Fibre Channel port, or NULL + */ +static struct fc_port * +fc_port_find_link_wwn ( struct fc_name *link_node_wwn ) { + struct fc_port *port; + + list_for_each_entry ( port, &fc_ports, list ) { + if ( fc_link_ok ( &port->link ) && + ( memcmp ( &port->link_node_wwn, link_node_wwn, + sizeof ( port->link_node_wwn ) ) == 0 ) ) { + return port; + } + } + return NULL; +} + +/****************************************************************************** + * + * Fibre Channel peers + * + ****************************************************************************** + */ + +/** + * Close Fibre Channel peer + * + * @v peer Fibre Channel peer + * @v rc Reason for close + */ +static void fc_peer_close ( struct fc_peer *peer, int rc ) { + + DBGC ( peer, "FCPEER %s closed: %s\n", + fc_ntoa ( &peer->node_wwn ) , strerror ( rc ) ); + + /* Sanity check */ + assert ( list_empty ( &peer->ulps ) ); + + /* Stop link timer */ + fc_link_stop ( &peer->link ); + + /* Shut down interfaces */ + intf_shutdown ( &peer->plogi, rc ); + + /* Remove from list of peers */ + list_del ( &peer->list ); + INIT_LIST_HEAD ( &peer->list ); +} + +/** + * Increment Fibre Channel peer active usage count + * + * @v peer Fibre Channel peer + */ +static void fc_peer_increment ( struct fc_peer *peer ) { + + /* Increment our usage count */ + peer->usage++; +} + +/** + * Decrement Fibre Channel peer active usage count + * + * @v peer Fibre Channel peer + */ +static void fc_peer_decrement ( struct fc_peer *peer ) { + + /* Sanity check */ + assert ( peer->usage > 0 ); + + /* Decrement our usage count and log out if we reach zero */ + if ( peer->usage-- == 0 ) + fc_peer_logout ( peer, 0 ); +} + +/** + * Log in Fibre Channel peer + * + * @v peer Fibre Channel peer + * @v port Fibre Channel port + * @v port_id Port ID + * @ret rc Return status code + */ +int fc_peer_login ( struct fc_peer *peer, struct fc_port *port, + struct fc_port_id *port_id ) { + struct fc_ulp *ulp; + struct fc_ulp *tmp; + + /* Perform implicit logout if logged in and details differ */ + if ( fc_link_ok ( &peer->link ) && + ( ( peer->port != port ) || + ( memcmp ( &peer->port_id, port_id, + sizeof ( peer->port_id ) ) !=0 ) ) ) { + fc_peer_logout ( peer, 0 ); + } + + /* Log in, if applicable */ + if ( ! fc_link_ok ( &peer->link ) ) { + + /* Record peer details */ + assert ( peer->port == NULL ); + peer->port = fc_port_get ( port ); + memcpy ( &peer->port_id, port_id, sizeof ( peer->port_id ) ); + DBGC ( peer, "FCPEER %s logged in via %s as %s\n", + fc_ntoa ( &peer->node_wwn ), peer->port->name, + fc_id_ntoa ( &peer->port_id ) ); + + /* Add login reference */ + fc_peer_get ( peer ); + } + + /* Record login */ + fc_link_up ( &peer->link ); + + /* Notify ULPs of link state change */ + list_for_each_entry_safe ( ulp, tmp, &peer->ulps, list ) + fc_link_examine ( &ulp->link ); + + return 0; +} + +/** + * Log out Fibre Channel peer + * + * @v peer Fibre Channel peer + * @v rc Reason for logout + */ +void fc_peer_logout ( struct fc_peer *peer, int rc ) { + struct fc_ulp *ulp; + struct fc_ulp *tmp; + + DBGC ( peer, "FCPEER %s logged out: %s\n", + fc_ntoa ( &peer->node_wwn ), strerror ( rc ) ); + + /* Drop login reference, if applicable */ + if ( fc_link_ok ( &peer->link ) ) + fc_peer_put ( peer ); + + /* Erase peer details */ + fc_port_put ( peer->port ); + peer->port = NULL; + + /* Record logout */ + fc_link_err ( &peer->link, rc ); + + /* Notify ULPs of link state change */ + list_for_each_entry_safe ( ulp, tmp, &peer->ulps, list ) + fc_link_examine ( &ulp->link ); + + /* Close peer if there are no active users */ + if ( peer->usage == 0 ) + fc_peer_close ( peer, rc ); +} + +/** + * Handle PLOGI completion + * + * @v peer Fibre Channel peer + * @v rc Reason for completion + */ +static void fc_peer_plogi_done ( struct fc_peer *peer, int rc ) { + + intf_restart ( &peer->plogi, rc ); + + if ( rc != 0 ) + fc_peer_logout ( peer, rc ); +} + +/** + * Examine Fibre Channel peer link state + * + * @ link Fibre Channel link state monitor + */ +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 */ + if ( peer->port && ( ! fc_link_ok ( &peer->port->link ) ) ) { + fc_peer_logout ( peer, -ENOTCONN ); + return; + } + + /* Do nothing if already logged in */ + if ( fc_link_ok ( &peer->link ) ) + return; + + DBGC ( peer, "FCPEER %s attempting login\n", + fc_ntoa ( &peer->node_wwn ) ); + + /* 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->node_wwn ); + if ( ! port ) { + DBGC ( peer, "FCPEER %s could not find a point-to-point " + "link\n", fc_ntoa ( &peer->node_wwn ) ); + fc_peer_logout ( peer, -ENOENT ); + 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->node_wwn ), strerror ( rc ) ); + fc_peer_logout ( peer, rc ); + return; + } +} + +/** Fibre Channel peer PLOGI interface operations */ +static struct interface_operation fc_peer_plogi_op[] = { + INTF_OP ( intf_close, struct fc_peer *, fc_peer_plogi_done ), +}; + +/** Fibre Channel peer PLOGI interface descriptor */ +static struct interface_descriptor fc_peer_plogi_desc = + INTF_DESC ( struct fc_peer, plogi, fc_peer_plogi_op ); + +/** + * Create Fibre Channel peer + * + * @v node_wwn Node name + * @ret peer Fibre Channel peer, or NULL + */ +static struct fc_peer * fc_peer_create ( const struct fc_name *node_wwn ) { + struct fc_peer *peer; + + /* Allocate and initialise structure */ + peer = zalloc ( sizeof ( *peer ) ); + if ( ! peer ) + return NULL; + ref_init ( &peer->refcnt, NULL ); + fc_link_init ( &peer->link, fc_peer_examine, &peer->refcnt ); + intf_init ( &peer->plogi, &fc_peer_plogi_desc, &peer->refcnt ); + list_add ( &peer->list, &fc_peers ); + memcpy ( &peer->node_wwn, node_wwn, sizeof ( peer->node_wwn ) ); + INIT_LIST_HEAD ( &peer->ulps ); + + /* Start link monitor */ + fc_link_start ( &peer->link ); + + DBGC ( peer, "FCPEER %s created\n", fc_ntoa ( &peer->node_wwn ) ); + return peer; +} + +/** + * Get Fibre Channel peer by node name + * + * @v node_wwn Node name + * @ret peer Fibre Channel peer, or NULL + */ +struct fc_peer * fc_peer_get_wwn ( const struct fc_name *node_wwn ) { + struct fc_peer *peer; + + /* Look for an existing peer */ + list_for_each_entry ( peer, &fc_peers, list ) { + if ( memcmp ( &peer->node_wwn, node_wwn, + sizeof ( peer->node_wwn ) ) == 0 ) + return fc_peer_get ( peer ); + } + + /* Create a new peer */ + peer = fc_peer_create ( node_wwn ); + if ( ! peer ) + return NULL; + + return peer; +} + +/** + * Get Fibre Channel peer by port ID + * + * @v port Fibre Channel port + * @v peer_port_id Peer port ID + * @ret peer Fibre Channel peer, or NULL + */ +struct fc_peer * fc_peer_get_port_id ( struct fc_port *port, + const struct fc_port_id *peer_port_id ){ + struct fc_peer *peer; + + /* Look for an existing peer */ + list_for_each_entry ( peer, &fc_peers, list ) { + if ( ( peer->port == port ) && + ( memcmp ( &peer->port_id, peer_port_id, + sizeof ( peer->port_id ) ) == 0 ) ) + return fc_peer_get ( peer ); + } + + /* Cannot create a new peer, since we have no port name to use */ + return NULL; +} + +/****************************************************************************** + * + * Fibre Channel upper-layer protocols + * + ****************************************************************************** + */ + +/** + * Close Fibre Channel upper-layer protocol + * + * @v ulp Fibre Channel upper-layer protocol + * @v rc Reason for close + */ +static void fc_ulp_close ( struct fc_ulp *ulp, int rc ) { + + DBGC ( ulp, "FCULP %s/%02x closed: %s\n", + fc_ntoa ( &ulp->peer->node_wwn ), ulp->type, strerror ( rc ) ); + + /* Sanity check */ + assert ( ulp->usage == 0 ); + + /* Stop link monitor */ + fc_link_stop ( &ulp->link ); + + /* Shut down interfaces */ + intf_shutdown ( &ulp->prli, rc ); + + /* Remove from list of ULPs */ + list_del ( &ulp->list ); + INIT_LIST_HEAD ( &ulp->list ); + + /* Drop peer reference */ + fc_peer_put ( ulp->peer ); + ulp->peer = NULL; +} + +/** + * Increment Fibre Channel upper-layer protocol active usage count + * + * @v ulp Fibre Channel ulp + */ +void fc_ulp_increment ( struct fc_ulp *ulp ) { + + /* Increment peer's usage count */ + fc_peer_increment ( ulp->peer ); + + /* Increment our usage count */ + ulp->usage++; +} + +/** + * Decrement Fibre Channel upper-layer protocol active usage count + * + * @v ulp Fibre Channel ulp + */ +void fc_ulp_decrement ( struct fc_ulp *ulp ) { + struct fc_peer *peer = ulp->peer; + + /* Sanity check */ + assert ( ulp->usage > 0 ); + + /* Decrement our usage count and log out if we reach zero */ + if ( ulp->usage-- == 0 ) + fc_ulp_logout ( ulp, 0 ); + + /* Decrement our peer's usage count */ + fc_peer_decrement ( peer ); +} + +/** + * Log in Fibre Channel upper-layer protocol + * + * @v ulp Fibre Channel upper-layer protocol + * @v param Service parameters + * @v param_len Length of service parameters + * @v originated Login was originated by us + * @ret rc Return status code + */ +int fc_ulp_login ( struct fc_ulp *ulp, const void *param, size_t param_len, + int originated ) { + + /* Perform implicit logout if logged in and service parameters differ */ + if ( fc_link_ok ( &ulp->link ) && + ( ( ulp->param_len != param_len ) || + ( memcmp ( ulp->param, param, ulp->param_len ) != 0 ) ) ) { + fc_ulp_logout ( ulp, 0 ); + } + + /* Log in, if applicable */ + if ( ! fc_link_ok ( &ulp->link ) ) { + + /* Record service parameters */ + assert ( ulp->param == NULL ); + assert ( ulp->param_len == 0 ); + ulp->param = malloc ( param_len ); + if ( ! ulp->param ) { + DBGC ( ulp, "FCULP %s/%02x could not record " + "parameters\n", + fc_ntoa ( &ulp->peer->node_wwn ), ulp->type ); + return -ENOMEM; + } + memcpy ( ulp->param, param, param_len ); + ulp->param_len = param_len; + DBGC ( ulp, "FCULP %s/%02x logged in with parameters:\n", + fc_ntoa ( &ulp->peer->node_wwn ), ulp->type ); + DBGC_HDA ( ulp, 0, ulp->param, ulp->param_len ); + + /* Add login reference */ + fc_ulp_get ( ulp ); + } + + /* Record login */ + fc_link_up ( &ulp->link ); + + /* Work around a bug in some versions of the Linux Fibre + * Channel stack, which fail to fully initialise image pairs + * established via a PRLI originated by the Linux stack + * itself. + */ + if ( originated ) + ulp->flags |= FC_ULP_ORIGINATED_LOGIN_OK; + if ( ! ( ulp->flags & FC_ULP_ORIGINATED_LOGIN_OK ) ) { + DBGC ( ulp, "FCULP %s/%02x sending extra PRLI to work around " + "Linux bug\n", + fc_ntoa ( &ulp->peer->node_wwn ), ulp->type ); + fc_link_start ( &ulp->link ); + } + + return 0; +} + +/** + * Log out Fibre Channel upper-layer protocol + * + * @v ulp Fibre Channel upper-layer protocol + * @v rc Reason for logout + */ +void fc_ulp_logout ( struct fc_ulp *ulp, int rc ) { + + DBGC ( ulp, "FCULP %s/%02x logged out: %s\n", + fc_ntoa ( &ulp->peer->node_wwn ), ulp->type, strerror ( rc ) ); + + /* Drop login reference, if applicable */ + if ( fc_link_ok ( &ulp->link ) ) + fc_ulp_put ( ulp ); + + /* Discard service parameters */ + free ( ulp->param ); + ulp->param = NULL; + ulp->param_len = 0; + ulp->flags = 0; + + /* Record logout */ + fc_link_err ( &ulp->link, rc ); + + /* Close ULP if there are no clients attached */ + if ( ulp->usage == 0 ) + fc_ulp_close ( ulp, rc ); +} + +/** + * Handle PRLI completion + * + * @v ulp Fibre Channel upper-layer protocol + * @v rc Reason for completion + */ +static void fc_ulp_prli_done ( struct fc_ulp *ulp, int rc ) { + + intf_restart ( &ulp->prli, rc ); + + if ( rc != 0 ) + fc_ulp_logout ( ulp, rc ); +} + +/** + * Examine Fibre Channel upper-layer protocol link state + * + * @ link Fibre Channel link state monitor + */ +static void fc_ulp_examine ( struct fc_link_state *link ) { + struct fc_ulp *ulp = container_of ( link, struct fc_ulp, link ); + int rc; + + /* Check to see if underlying peer link has gone down */ + if ( ! fc_link_ok ( &ulp->peer->link ) ) { + fc_ulp_logout ( ulp, -ENOTCONN ); + return; + } + + /* Do nothing if already logged in */ + if ( fc_link_ok ( &ulp->link ) && + ( ulp->flags & FC_ULP_ORIGINATED_LOGIN_OK ) ) + return; + + DBGC ( ulp, "FCULP %s/%02x attempting login\n", + fc_ntoa ( &ulp->peer->node_wwn ), ulp->type ); + + /* Try to create PRLI ELS */ + intf_restart ( &ulp->prli, -ECANCELED ); + if ( ( rc = fc_els_prli ( &ulp->prli, ulp->peer->port, + &ulp->peer->port_id, ulp->type ) ) != 0 ) { + DBGC ( ulp, "FCULP %s/%02x could not initiate PRLI: %s\n", + fc_ntoa ( &ulp->peer->node_wwn ), ulp->type, + strerror ( rc ) ); + fc_ulp_logout ( ulp, rc ); + return; + } +} + +/** Fibre Channel upper-layer protocol PRLI interface operations */ +static struct interface_operation fc_ulp_prli_op[] = { + INTF_OP ( intf_close, struct fc_ulp *, fc_ulp_prli_done ), +}; + +/** Fibre Channel upper-layer protocol PRLI interface descriptor */ +static struct interface_descriptor fc_ulp_prli_desc = + INTF_DESC ( struct fc_ulp, prli, fc_ulp_prli_op ); + +/** + * Create Fibre Channel upper-layer protocl + * + * @v peer Fibre Channel peer + * @v type Type + * @ret ulp Fibre Channel upper-layer protocol, or NULL + */ +static struct fc_ulp * fc_ulp_create ( struct fc_peer *peer, + unsigned int type ) { + struct fc_ulp *ulp; + + /* Allocate and initialise structure */ + ulp = zalloc ( sizeof ( *ulp ) ); + if ( ! ulp ) + return NULL; + ref_init ( &ulp->refcnt, NULL ); + fc_link_init ( &ulp->link, fc_ulp_examine, &ulp->refcnt ); + intf_init ( &ulp->prli, &fc_ulp_prli_desc, &ulp->refcnt ); + ulp->peer = fc_peer_get ( peer ); + list_add ( &ulp->list, &peer->ulps ); + ulp->type = type; + + /* Start link state monitor */ + fc_link_start ( &ulp->link ); + + DBGC ( ulp, "FCULP %s/%02x created\n", + fc_ntoa ( &ulp->peer->node_wwn ), ulp->type ); + return ulp; +} + +/** + * Get Fibre Channel upper-layer protocol by peer and type + * + * @v peer Fibre Channel peer + * @v type Type + * @ret ulp Fibre Channel upper-layer protocol, or NULL + */ +static struct fc_ulp * fc_ulp_get_type ( struct fc_peer *peer, + unsigned int type ) { + struct fc_ulp *ulp; + + /* Look for an existing ULP */ + list_for_each_entry ( ulp, &peer->ulps, list ) { + if ( ulp->type == type ) + return fc_ulp_get ( ulp ); + } + + /* Create a new ULP */ + ulp = fc_ulp_create ( peer, type ); + if ( ! ulp ) + return NULL; + + return ulp; +} + +/** + * Get Fibre Channel upper-layer protocol by port name and type + * + * @v node_wwn Port name + * @v type Type + * @ret ulp Fibre Channel upper-layer protocol, or NULL + */ +struct fc_ulp * fc_ulp_get_wwn_type ( const struct fc_name *node_wwn, + unsigned int type ) { + struct fc_ulp *ulp; + struct fc_peer *peer; + + /* Get peer */ + peer = fc_peer_get_wwn ( node_wwn ); + if ( ! peer ) + goto err_peer_get_wwn; + + /* Get ULP */ + ulp = fc_ulp_get_type ( peer, type ); + if ( ! ulp ) + goto err_ulp_get_type; + + /* Drop temporary reference to peer */ + fc_peer_put ( peer ); + + return ulp; + + fc_ulp_put ( ulp ); + err_ulp_get_type: + fc_peer_put ( peer ); + err_peer_get_wwn: + return NULL; +} + +/** + * Get Fibre Channel upper-layer protocol by port ID and type + * + * @v port Fibre Channel port + * @v peer_port_id Peer port ID + * @v type Type + * @ret ulp Fibre Channel upper-layer protocol, or NULL + */ +struct fc_ulp * fc_ulp_get_port_id_type ( struct fc_port *port, + const struct fc_port_id *peer_port_id, + unsigned int type ) { + struct fc_ulp *ulp; + struct fc_peer *peer; + + /* Get peer */ + peer = fc_peer_get_port_id ( port, peer_port_id ); + if ( ! peer ) + goto err_peer_get_wwn; + + /* Get ULP */ + ulp = fc_ulp_get_type ( peer, type ); + if ( ! ulp ) + goto err_ulp_get_type; + + /* Drop temporary reference to peer */ + fc_peer_put ( peer ); + + return ulp; + + fc_ulp_put ( ulp ); + err_ulp_get_type: + fc_peer_put ( peer ); + err_peer_get_wwn: + return NULL; +} diff --git a/src/net/fcels.c b/src/net/fcels.c new file mode 100644 index 00000000..63063ca3 --- /dev/null +++ b/src/net/fcels.c @@ -0,0 +1,1162 @@ +/* + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#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 = { + .flags = ( fc_els_is_request ( els ) ? + XFER_FL_OVER : ( XFER_FL_RESPONSE | XFER_FL_OUT ) ), + }; + int rc; + + DBGC2 ( els, FCELS_FMT " transmitting:\n", FCELS_ARGS ( els ) ); + DBGC2_HDA ( els, 0, data, len ); + + /* 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 __unused ) { + struct fc_els_frame_common *frame = iobuf->data; + size_t len = iob_len ( iobuf ); + int ( * rx ) ( struct fc_els *els, const void *data, size_t len ); + int rc; + + /* Sanity check */ + if ( len < sizeof ( *frame ) ) { + DBGC ( els, FCELS_FMT " received underlength frame:\n", + FCELS_ARGS ( els ) ); + DBGC_HDA ( els, 0, frame, len ); + rc = -EINVAL; + goto done; + } + + /* 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; + } + + /* Determine handler, if necessary */ + if ( ! els->handler ) + els->handler = fc_els_detect ( els, frame, len ); + if ( ! els->handler ) + els->handler = &fc_els_unknown_handler; + + DBGC2 ( els, FCELS_FMT " received:\n", FCELS_ARGS ( els ) ); + DBGC2_HDA ( els, 0, frame, len ); + + /* Handle received frame */ + rx = ( fc_els_is_request ( els ) ? + els->handler->rx_response : els->handler->rx_request ); + if ( ( rc = rx ( els, frame, len ) ) != 0 ) { + DBGC ( els, FCELS_FMT " could not handle received frame: " + "%s\n", FCELS_ARGS ( els ), strerror ( rc ) ); + DBGC_HDA ( els, 0, frame, len ); + goto done; + } + + /* Free I/O buffer. Do this before transmitting response to + * minimise memory consumption. + */ + free_iob ( iob_disown ( iobuf ) ); + + /* Transmit response if applicable */ + if ( ( ! fc_els_is_request ( els ) ) && + ( ( rc = els->handler->tx_response ( els ) ) != 0 ) ) { + DBGC ( els, FCELS_FMT " could not transmit response: %s\n", + FCELS_ARGS ( els ), strerror ( rc ) ); + goto done; + } + + done: + /* Free I/O buffer */ + free_iob ( iobuf ); + + /* Close transaction */ + fc_els_close ( els, rc ); + + return rc; +} + +/** Fibre Channel ELS exchange interface operations */ +static struct interface_operation fc_els_xchg_op[] = { + INTF_OP ( xfer_deliver, struct fc_els *, fc_els_rx ), + INTF_OP ( intf_close, struct fc_els *, fc_els_close ), +}; + +/** Fibre Channel ELS exchange interface descriptor */ +static struct interface_descriptor fc_els_xchg_desc = + INTF_DESC ( struct fc_els, xchg, fc_els_xchg_op ); + +/** Fibre Channel ELS job control interface operations */ +static struct interface_operation fc_els_job_op[] = { + INTF_OP ( intf_close, struct fc_els *, fc_els_close ), +}; + +/** Fibre Channel ELS job control interface descriptor */ +static struct interface_descriptor fc_els_job_desc = + INTF_DESC ( struct fc_els, job, fc_els_job_op ); + +/** + * Fibre Channel ELS process + * + * @v process Process + */ +static void fc_els_step ( struct process *process ) { + struct fc_els *els = + container_of ( process, struct fc_els, process ); + int rc; + + /* Sanity check */ + assert ( fc_els_is_request ( els ) ); + + /* Stop process */ + process_del ( &els->process ); + + /* Create exchange */ + if ( ( rc = fc_xchg_originate ( &els->xchg, els->port, + &els->peer_port_id, + FC_TYPE_ELS ) ) != 0 ) { + DBGC ( els, FCELS_FMT " could not create exchange: %s\n", + FCELS_ARGS ( els ), strerror ( rc ) ); + fc_els_close ( els, rc ); + return; + } + + /* Transmit request */ + if ( ( rc = els->handler->tx_request ( els ) ) != 0 ) { + DBGC ( els, FCELS_FMT " could not transmit request: %s\n", + FCELS_ARGS ( els ), strerror ( rc ) ); + fc_els_close ( els, rc ); + return; + } +} + +/** + * Create ELS transaction + * + * @v port Fibre Channel port + * @v port_id Local port ID + * @v peer_port_id Peer port ID + * @ret els Fibre Channel ELS transaction, or NULL + */ +static struct fc_els * fc_els_create ( struct fc_port *port, + struct fc_port_id *port_id, + struct fc_port_id *peer_port_id ) { + struct fc_els *els; + + /* Allocate and initialise structure */ + els = zalloc ( sizeof ( *els ) ); + if ( ! els ) + return NULL; + ref_init ( &els->refcnt, fc_els_free ); + intf_init ( &els->job, &fc_els_job_desc, &els->refcnt ); + intf_init ( &els->xchg, &fc_els_xchg_desc, &els->refcnt ); + process_init_stopped ( &els->process, fc_els_step, &els->refcnt ); + els->port = fc_port_get ( port ); + memcpy ( &els->port_id, port_id, sizeof ( els->port_id ) ); + memcpy ( &els->peer_port_id, peer_port_id, + sizeof ( els->peer_port_id ) ); + return els; +} + +/** + * Create ELS request + * + * @v job Parent job-control interface + * @v port Fibre Channel port + * @v peer_port_id Peer port ID + * @v handler ELS handler + * @ret rc Return status code + */ +int fc_els_request ( struct interface *job, struct fc_port *port, + struct fc_port_id *peer_port_id, + struct fc_els_handler *handler ) { + struct fc_els *els; + + /* Allocate and initialise structure */ + els = fc_els_create ( port, &port->port_id, peer_port_id ); + if ( ! els ) + return -ENOMEM; + els->handler = handler; + els->flags = FC_ELS_REQUEST; + process_add ( &els->process ); + + /* Attach to parent job interface, mortalise self, and return */ + intf_plug_plug ( &els->job, job ); + ref_put ( &els->refcnt ); + return 0; +} + +/** + * Create ELS response + * + * @v xchg Exchange interface + * @v port Fibre Channel port + * @v port_id Local port ID + * @v peer_port_id Peer port ID + * @ret rc Return status code + */ +static int fc_els_respond ( struct interface *xchg, struct fc_port *port, + struct fc_port_id *port_id, + struct fc_port_id *peer_port_id ) { + struct fc_els *els; + + /* Allocate and initialise structure */ + els = fc_els_create ( port, port_id, peer_port_id ); + if ( ! els ) + return -ENOMEM; + + /* Attach to exchange interface, mortalise self, and return */ + intf_plug_plug ( &els->xchg, xchg ); + ref_put ( &els->refcnt ); + return 0; +} + +/** Fibre Channel ELS responder */ +struct fc_responder fc_els_responder __fc_responder = { + .type = FC_TYPE_ELS, + .respond = fc_els_respond, +}; + +/****************************************************************************** + * + * Unknown ELS handler + * + ****************************************************************************** + */ + +/** + * Transmit unknown ELS request + * + * @v els Fibre Channel ELS transaction + * @ret rc Return status code + */ +static int fc_els_unknown_tx_request ( struct fc_els *els __unused ) { + return -ENOTSUP; +} + +/** + * Transmit unknown ELS response + * + * @v els Fibre Channel ELS transaction + * @ret rc Return status code + */ +static int fc_els_unknown_tx_response ( struct fc_els *els ) { + struct fc_ls_rjt_frame ls_rjt; + + /* Construct LS_RJT */ + memset ( &ls_rjt, 0, sizeof ( ls_rjt ) ); + ls_rjt.command = FC_ELS_LS_RJT; + ls_rjt.reason = FC_ELS_RJT_UNSUPPORTED; + + /* Transmit LS_RJT */ + return fc_els_tx ( els, &ls_rjt, sizeof ( ls_rjt ) ); +} + +/** + * Receive unknown ELS request + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_unknown_rx_request ( struct fc_els *els, const void *data, + size_t len ) { + + DBGC ( els, FCELS_FMT ":\n", FCELS_ARGS ( els ) ); + DBGC_HDA ( els, 0, data, len ); + return 0; +} + +/** + * Receive unknown ELS response + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_unknown_rx_response ( struct fc_els *els __unused, + const void *data __unused, + size_t len __unused ) { + assert ( 0 ); + return -EINVAL; +} + +/** + * Detect unknown ELS + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_unknown_detect ( struct fc_els *els __unused, + const void *data __unused, + size_t len __unused ) { + return -ENOTSUP; +} + +/** Unknown ELS handler */ +struct fc_els_handler fc_els_unknown_handler __fc_els_handler = { + .name = "UNKNOWN", + .tx_request = fc_els_unknown_tx_request, + .tx_response = fc_els_unknown_tx_response, + .rx_request = fc_els_unknown_rx_request, + .rx_response = fc_els_unknown_rx_response, + .detect = fc_els_unknown_detect, +}; + +/****************************************************************************** + * + * FLOGI + * + ****************************************************************************** + */ + +/** + * Transmit FLOGI + * + * @v els Fibre Channel ELS transaction + * @ret rc Return status code + */ +static int fc_els_flogi_tx ( struct fc_els *els ) { + struct fc_login_frame flogi; + + /* Construct FLOGI */ + memset ( &flogi, 0, sizeof ( flogi ) ); + flogi.command = fc_els_tx_command ( els, FC_ELS_FLOGI ); + flogi.common.version = htons ( FC_LOGIN_VERSION ); + flogi.common.credit = htons ( FC_LOGIN_DEFAULT_B2B ); + flogi.common.flags = htons ( FC_LOGIN_CONTINUOUS_OFFSET ); + flogi.common.mtu = htons ( FC_LOGIN_DEFAULT_MTU ); + memcpy ( &flogi.port_wwn, &els->port->port_wwn, + sizeof ( flogi.port_wwn ) ); + memcpy ( &flogi.node_wwn, &els->port->node_wwn, + sizeof ( flogi.node_wwn ) ); + flogi.class3.flags = htons ( FC_LOGIN_CLASS_VALID | + FC_LOGIN_CLASS_SEQUENTIAL ); + + /* Transmit FLOGI */ + return fc_els_tx ( els, &flogi, sizeof ( flogi ) ); +} + +/** + * Receive FLOGI + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_flogi_rx ( struct fc_els *els, const void *data, + size_t len ) { + const struct fc_login_frame *flogi = data; + int has_fabric; + int rc; + + /* Sanity check */ + if ( len < sizeof ( *flogi ) ) { + DBGC ( els, FCELS_FMT " received underlength frame:\n", + FCELS_ARGS ( els ) ); + DBGC_HDA ( els, 0, data, len ); + return -EINVAL; + } + + /* Extract parameters */ + has_fabric = ( flogi->common.flags & htons ( FC_LOGIN_F_PORT ) ); + DBGC ( els, FCELS_FMT " has node %s\n", FCELS_ARGS ( els ), + fc_ntoa ( &flogi->node_wwn ) ); + DBGC ( els, FCELS_FMT " has port %s\n", FCELS_ARGS ( els ), + fc_ntoa ( &flogi->port_wwn ) ); + if ( has_fabric ) { + DBGC ( els, FCELS_FMT " has fabric with local ID %s\n", + FCELS_ARGS ( els ), 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; + } + + return 0; +} + +/** + * Detect FLOGI + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_flogi_detect ( struct fc_els *els __unused, const void *data, + size_t len __unused ) { + const struct fc_login_frame *flogi = data; + + /* Check for FLOGI */ + if ( flogi->command != FC_ELS_FLOGI ) + return -EINVAL; + + return 0; +} + +/** FLOGI ELS handler */ +struct fc_els_handler fc_els_flogi_handler __fc_els_handler = { + .name = "FLOGI", + .tx_request = fc_els_flogi_tx, + .tx_response = fc_els_flogi_tx, + .rx_request = fc_els_flogi_rx, + .rx_response = fc_els_flogi_rx, + .detect = fc_els_flogi_detect, +}; + +/** + * Create FLOGI request + * + * @v parent Parent interface + * @v port Fibre Channel port + * @ret rc Return status code + */ +int fc_els_flogi ( struct interface *parent, struct fc_port *port ) { + + return fc_els_request ( parent, port, &fc_f_port_id, + &fc_els_flogi_handler ); +} + +/****************************************************************************** + * + * PLOGI + * + ****************************************************************************** + */ + +/** + * Transmit PLOGI + * + * @v els Fibre Channel ELS transaction + * @ret rc Return status code + */ +static int fc_els_plogi_tx ( struct fc_els *els ) { + struct fc_login_frame plogi; + + /* Construct PLOGI */ + memset ( &plogi, 0, sizeof ( plogi ) ); + plogi.command = fc_els_tx_command ( els, FC_ELS_PLOGI ); + plogi.common.version = htons ( FC_LOGIN_VERSION ); + plogi.common.credit = htons ( FC_LOGIN_DEFAULT_B2B ); + plogi.common.flags = htons ( FC_LOGIN_CONTINUOUS_OFFSET ); + plogi.common.mtu = htons ( FC_LOGIN_DEFAULT_MTU ); + plogi.common.u.plogi.max_seq = htons ( FC_LOGIN_DEFAULT_MAX_SEQ ); + plogi.common.u.plogi.rel_offs = htons ( FC_LOGIN_DEFAULT_REL_OFFS ); + plogi.common.e_d_tov = htonl ( FC_LOGIN_DEFAULT_E_D_TOV ); + memcpy ( &plogi.port_wwn, &els->port->port_wwn, + sizeof ( plogi.port_wwn ) ); + memcpy ( &plogi.node_wwn, &els->port->node_wwn, + sizeof ( plogi.node_wwn ) ); + plogi.class3.flags = htons ( FC_LOGIN_CLASS_VALID | + FC_LOGIN_CLASS_SEQUENTIAL ); + plogi.class3.mtu = htons ( FC_LOGIN_DEFAULT_MTU ); + plogi.class3.max_seq = htons ( FC_LOGIN_DEFAULT_MAX_SEQ ); + plogi.class3.max_seq_per_xchg = 1; + + /* Transmit PLOGI */ + return fc_els_tx ( els, &plogi, sizeof ( plogi ) ); +} + +/** + * Receive PLOGI + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_plogi_rx ( struct fc_els *els, const void *data, + size_t len ) { + const struct fc_login_frame *plogi = data; + struct fc_peer *peer; + int rc; + + /* Sanity checks */ + if ( len < sizeof ( *plogi ) ) { + DBGC ( els, FCELS_FMT " received underlength frame:\n", + FCELS_ARGS ( els ) ); + DBGC_HDA ( els, 0, data, len ); + rc = -EINVAL; + goto err_sanity; + } + if ( ! fc_link_ok ( &els->port->link ) ) { + DBGC ( els, FCELS_FMT " received while port link is down\n", + FCELS_ARGS ( els ) ); + rc = -EINVAL; + goto err_sanity; + } + + /* Extract parameters */ + DBGC ( els, FCELS_FMT " has node %s\n", FCELS_ARGS ( els ), + fc_ntoa ( &plogi->node_wwn ) ); + DBGC ( els, FCELS_FMT " has port %s as %s\n", + FCELS_ARGS ( els ), fc_ntoa ( &plogi->port_wwn ), + fc_id_ntoa ( &els->peer_port_id ) ); + + /* Get peer */ + peer = fc_peer_get_wwn ( &plogi->node_wwn ); + if ( ! peer ) { + DBGC ( els, FCELS_FMT " could not create peer\n", + FCELS_ARGS ( els ) ); + rc = -ENOMEM; + goto err_peer_get_wwn; + } + + /* Record login */ + if ( ( rc = fc_peer_login ( peer, els->port, + &els->peer_port_id ) ) != 0 ) { + DBGC ( els, FCELS_FMT " could not log in peer: %s\n", + FCELS_ARGS ( els ), strerror ( rc ) ); + goto err_login; + } + + /* Drop temporary reference to peer */ + fc_peer_put ( peer ); + + return 0; + + err_login: + fc_peer_put ( peer ); + err_peer_get_wwn: + err_sanity: + return rc; +} + +/** + * Detect PLOGI + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_plogi_detect ( struct fc_els *els __unused, const void *data, + size_t len __unused ) { + const struct fc_login_frame *plogi = data; + + /* Check for PLOGI */ + if ( plogi->command != FC_ELS_PLOGI ) + return -EINVAL; + + return 0; +} + +/** PLOGI ELS handler */ +struct fc_els_handler fc_els_plogi_handler __fc_els_handler = { + .name = "PLOGI", + .tx_request = fc_els_plogi_tx, + .tx_response = fc_els_plogi_tx, + .rx_request = fc_els_plogi_rx, + .rx_response = fc_els_plogi_rx, + .detect = fc_els_plogi_detect, +}; + +/** + * Create PLOGI request + * + * @v parent Parent interface + * @v port Fibre Channel port + * @v peer_port_id Peer port ID + * @ret rc Return status code + */ +int fc_els_plogi ( struct interface *parent, struct fc_port *port, + struct fc_port_id *peer_port_id ) { + + return fc_els_request ( parent, port, peer_port_id, + &fc_els_plogi_handler ); +} + +/****************************************************************************** + * + * LOGO + * + ****************************************************************************** + */ + +/** + * Transmit LOGO request + * + * @v els Fibre Channel ELS transaction + * @ret rc Return status code + */ +static int fc_els_logo_tx_request ( struct fc_els *els ) { + struct fc_logout_request_frame logo; + + /* Construct LOGO */ + memset ( &logo, 0, sizeof ( logo ) ); + logo.command = FC_ELS_LOGO; + memcpy ( &logo.port_id, &els->port->port_id, sizeof ( logo.port_id ) ); + memcpy ( &logo.port_wwn, &els->port->port_wwn, + sizeof ( logo.port_wwn ) ); + + /* Transmit LOGO */ + return fc_els_tx ( els, &logo, sizeof ( logo ) ); +} + +/** + * Transmit LOGO response + * + * @v els Fibre Channel ELS transaction + * @ret rc Return status code + */ +static int fc_els_logo_tx_response ( struct fc_els *els ) { + struct fc_logout_response_frame logo; + + /* Construct LOGO */ + memset ( &logo, 0, sizeof ( logo ) ); + logo.command = FC_ELS_LS_ACC; + + /* Transmit LOGO */ + return fc_els_tx ( els, &logo, sizeof ( logo ) ); +} + +/** + * Log out individual peer or whole port as applicable + * + * @v els Fibre Channel ELS transaction + * @v port_id Peer port ID + */ +static void fc_els_logo_logout ( struct fc_els *els, + const struct fc_port_id *peer_port_id ) { + struct fc_peer *peer; + + if ( ( memcmp ( peer_port_id, &fc_f_port_id, + sizeof ( *peer_port_id ) ) == 0 ) || + ( memcmp ( peer_port_id, &els->port->port_id, + sizeof ( *peer_port_id ) ) == 0 ) ) { + fc_port_logout ( els->port, 0 ); + } else { + peer = fc_peer_get_port_id ( els->port, peer_port_id ); + if ( peer ) { + fc_peer_logout ( peer, 0 ); + fc_peer_put ( peer ); + } + } +} + +/** + * Receive LOGO request + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_logo_rx_request ( struct fc_els *els, const void *data, + size_t len ) { + const struct fc_logout_request_frame *logo = data; + + /* Sanity check */ + if ( len < sizeof ( *logo ) ) { + DBGC ( els, FCELS_FMT " received underlength frame:\n", + FCELS_ARGS ( els ) ); + DBGC_HDA ( els, 0, data, len ); + return -EINVAL; + } + + DBGC ( els, FCELS_FMT " has port %s as %s\n", FCELS_ARGS ( els ), + fc_ntoa ( &logo->port_wwn ), fc_id_ntoa ( &logo->port_id ) ); + + /* Log out individual peer or whole port as applicable */ + fc_els_logo_logout ( els, &logo->port_id ); + + return 0; +} + +/** + * Receive LOGO response + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_logo_rx_response ( struct fc_els *els, + const void *data __unused, + size_t len __unused ) { + + /* Log out individual peer or whole port as applicable */ + fc_els_logo_logout ( els, &els->port_id ); + + return 0; +} + +/** + * Detect LOGO + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_logo_detect ( struct fc_els *els __unused, const void *data, + size_t len __unused ) { + const struct fc_logout_request_frame *logo = data; + + /* Check for LOGO */ + if ( logo->command != FC_ELS_LOGO ) + return -EINVAL; + + return 0; +} + +/** LOGO ELS handler */ +struct fc_els_handler fc_els_logo_handler __fc_els_handler = { + .name = "LOGO", + .tx_request = fc_els_logo_tx_request, + .tx_response = fc_els_logo_tx_response, + .rx_request = fc_els_logo_rx_request, + .rx_response = fc_els_logo_rx_response, + .detect = fc_els_logo_detect, +}; + +/** + * Create LOGO request + * + * @v parent Parent interface + * @v port Fibre Channel port + * @v peer_port_id Peer port ID + * @ret rc Return status code + */ +int fc_els_logo ( struct interface *parent, struct fc_port *port, + struct fc_port_id *peer_port_id ) { + + return fc_els_request ( parent, port, peer_port_id, + &fc_els_logo_handler ); +} + +/****************************************************************************** + * + * PRLI + * + ****************************************************************************** + */ + +/** + * Find PRLI descriptor + * + * @v type Upper-layer protocol type + * @ret descriptor PRLI descriptor, or NULL + */ +static struct fc_els_prli_descriptor * +fc_els_prli_descriptor ( unsigned int type ) { + struct fc_els_prli_descriptor *descriptor; + + for_each_table_entry ( descriptor, FC_ELS_PRLI_DESCRIPTORS ) { + if ( descriptor->type == type ) + return descriptor; + } + return NULL; +} + +/** + * Transmit PRLI + * + * @v els Fibre Channel ELS transaction + * @v descriptor ELS PRLI descriptor + * @v param Service parameters + * @ret rc Return status code + */ +int fc_els_prli_tx ( struct fc_els *els, + struct fc_els_prli_descriptor *descriptor, void *param ) { + struct { + struct fc_prli_frame frame; + uint8_t param[descriptor->param_len]; + } __attribute__ (( packed )) prli; + struct fc_ulp *ulp; + int rc; + + /* Get ULP */ + ulp = fc_ulp_get_port_id_type ( els->port, &els->peer_port_id, + descriptor->type ); + if ( ! ulp ) { + rc = -ENOMEM; + goto err_get_port_id_type; + } + + /* Build frame for transmission */ + memset ( &prli, 0, sizeof ( prli ) ); + prli.frame.command = fc_els_tx_command ( els, FC_ELS_PRLI ); + prli.frame.page_len = + ( sizeof ( prli.frame.page ) + sizeof ( prli.param ) ); + prli.frame.len = htons ( sizeof ( prli ) ); + prli.frame.page.type = descriptor->type; + if ( fc_els_is_request ( els ) ) { + prli.frame.page.flags |= htons ( FC_PRLI_ESTABLISH ); + } else if ( fc_link_ok ( &ulp->link ) ) { + prli.frame.page.flags |= htons ( FC_PRLI_ESTABLISH | + FC_PRLI_RESPONSE_SUCCESS ); + } + memcpy ( &prli.param, param, sizeof ( prli.param ) ); + + /* Transmit frame */ + if ( ( rc = fc_els_tx ( els, &prli, sizeof ( prli ) ) ) != 0 ) + goto err_tx; + + /* Drop temporary reference to ULP */ + fc_ulp_put ( ulp ); + + return 0; + + err_tx: + fc_ulp_put ( ulp ); + err_get_port_id_type: + return rc; +} + +/** + * Receive PRLI + * + * @v els Fibre Channel ELS transaction + * @v descriptor ELS PRLI descriptor + * @v frame ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +int fc_els_prli_rx ( struct fc_els *els, + struct fc_els_prli_descriptor *descriptor, + const void *data, size_t len ) { + const struct { + struct fc_prli_frame frame; + uint8_t param[descriptor->param_len]; + } __attribute__ (( packed )) *prli = data; + struct fc_ulp *ulp; + int rc; + + /* Sanity check */ + if ( len < sizeof ( *prli ) ) { + DBGC ( els, FCELS_FMT " received underlength frame:\n", + FCELS_ARGS ( els ) ); + DBGC_HDA ( els, 0, data, len ); + rc = -EINVAL; + goto err_sanity; + } + + DBGC ( els, FCELS_FMT " has parameters:\n", FCELS_ARGS ( els ) ); + DBGC_HDA ( els, 0, prli->param, sizeof ( prli->param ) ); + + /* Get ULP */ + ulp = fc_ulp_get_port_id_type ( els->port, &els->peer_port_id, + descriptor->type ); + if ( ! ulp ) { + rc = -ENOMEM; + goto err_get_port_id_type; + } + + /* Sanity check */ + if ( ! fc_link_ok ( &ulp->peer->link ) ) { + DBGC ( els, FCELS_FMT " received while peer link is down\n", + FCELS_ARGS ( els ) ); + rc = -EINVAL; + goto err_link; + } + + /* Log in ULP, if applicable */ + if ( prli->frame.page.flags & htons ( FC_PRLI_ESTABLISH ) ) { + if ( ( rc = fc_ulp_login ( ulp, prli->param, + sizeof ( prli->param ), + fc_els_is_request ( els ) ) ) != 0 ){ + DBGC ( els, FCELS_FMT " could not log in ULP: %s\n", + FCELS_ARGS ( els ), strerror ( rc ) ); + goto err_login; + } + } else { + if ( fc_els_is_request ( els ) ) { + fc_ulp_logout ( ulp, -EACCES ); + } else { + /* This is just an information-gathering PRLI; do not + * log in or out + */ + } + } + + /* Drop temporary reference to ULP */ + fc_ulp_put ( ulp ); + + return 0; + + err_login: + err_link: + fc_ulp_put ( ulp ); + err_get_port_id_type: + err_sanity: + return rc; +} + +/** + * Detect PRLI + * + * @v els Fibre Channel ELS transaction + * @v descriptor ELS PRLI descriptor + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +int fc_els_prli_detect ( struct fc_els *els __unused, + struct fc_els_prli_descriptor *descriptor, + const void *data, size_t len ) { + const struct { + struct fc_prli_frame frame; + uint8_t param[descriptor->param_len]; + } __attribute__ (( packed )) *prli = data; + + /* Check for PRLI */ + if ( prli->frame.command != FC_ELS_PRLI ) + return -EINVAL; + + /* Check for sufficient length to contain service parameter page */ + if ( len < sizeof ( *prli ) ) + return -EINVAL; + + /* Check for upper-layer protocol type */ + if ( prli->frame.page.type != descriptor->type ) + return -EINVAL; + + return 0; +} + +/** + * Create PRLI request + * + * @v parent Parent interface + * @v port Fibre Channel port + * @v peer_port_id Peer port ID + * @v type Upper-layer protocol type + * @ret rc Return status code + */ +int fc_els_prli ( struct interface *parent, struct fc_port *port, + struct fc_port_id *peer_port_id, unsigned int type ) { + struct fc_els_prli_descriptor *descriptor; + + /* Find a PRLI descriptor */ + descriptor = fc_els_prli_descriptor ( type ); + if ( ! descriptor ) + return -ENOTSUP; + + return fc_els_request ( parent, port, peer_port_id, + descriptor->handler ); +} + +/****************************************************************************** + * + * RTV + * + ****************************************************************************** + */ + +/** + * Transmit RTV response + * + * @v els Fibre Channel ELS transaction + * @ret rc Return status code + */ +static int fc_els_rtv_tx_response ( struct fc_els *els ) { + struct fc_rtv_response_frame rtv; + + /* Construct RTV */ + memset ( &rtv, 0, sizeof ( rtv ) ); + rtv.command = FC_ELS_LS_ACC; + rtv.e_d_tov = htonl ( FC_LOGIN_DEFAULT_E_D_TOV ); + + /* Transmit RTV */ + return fc_els_tx ( els, &rtv, sizeof ( rtv ) ); +} + +/** + * Receive RTV request + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_rtv_rx_request ( struct fc_els *els, + const void *data __unused, + size_t len __unused ) { + + DBGC ( els, FCELS_FMT "\n", FCELS_ARGS ( els ) ); + + /* Nothing to do */ + return 0; +} + +/** + * Detect RTV + * + * @v els Fibre Channel ELS transaction + * @v data ELS frame + * @v len Length of ELS frame + * @ret rc Return status code + */ +static int fc_els_rtv_detect ( struct fc_els *els __unused, const void *data, + size_t len __unused ) { + const struct fc_rtv_request_frame *rtv = data; + + /* Check for RTV */ + if ( rtv->command != FC_ELS_RTV ) + return -EINVAL; + + return 0; +} + +/** RTV ELS handler */ +struct fc_els_handler fc_els_rtv_handler __fc_els_handler = { + .name = "RTV", + .tx_request = fc_els_unknown_tx_request, + .tx_response = fc_els_rtv_tx_response, + .rx_request = fc_els_rtv_rx_request, + .rx_response = fc_els_unknown_rx_response, + .detect = fc_els_rtv_detect, +};