| /* |
| * Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of the |
| * License, or any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301, USA. |
| * |
| * You can also choose to distribute this program under the terms of |
| * the Unmodified Binary Distribution Licence (as given in the file |
| * COPYING.UBDL), provided that you have satisfied its requirements. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); |
| |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <byteswap.h> |
| #include <ipxe/if_ether.h> |
| #include <ipxe/if_arp.h> |
| #include <ipxe/iobuf.h> |
| #include <ipxe/interface.h> |
| #include <ipxe/xfer.h> |
| #include <ipxe/netdevice.h> |
| #include <ipxe/ethernet.h> |
| #include <ipxe/vlan.h> |
| #include <ipxe/features.h> |
| #include <ipxe/errortab.h> |
| #include <ipxe/device.h> |
| #include <ipxe/crc32.h> |
| #include <ipxe/retry.h> |
| #include <ipxe/timer.h> |
| #include <ipxe/fc.h> |
| #include <ipxe/fip.h> |
| #include <ipxe/fcoe.h> |
| |
| /** @file |
| * |
| * FCoE protocol |
| * |
| */ |
| |
| FEATURE ( FEATURE_PROTOCOL, "FCoE", DHCP_EB_FEATURE_FCOE, 1 ); |
| |
| /* Disambiguate the various error causes */ |
| #define EINVAL_UNDERLENGTH __einfo_error ( EINFO_EINVAL_UNDERLENGTH ) |
| #define EINFO_EINVAL_UNDERLENGTH \ |
| __einfo_uniqify ( EINFO_EINVAL, 0x01, "Underlength packet" ) |
| #define EINVAL_SOF __einfo_error ( EINFO_EINVAL_SOF ) |
| #define EINFO_EINVAL_SOF \ |
| __einfo_uniqify ( EINFO_EINVAL, 0x02, "Invalid SoF delimiter" ) |
| #define EINVAL_CRC __einfo_error ( EINFO_EINVAL_CRC ) |
| #define EINFO_EINVAL_CRC \ |
| __einfo_uniqify ( EINFO_EINVAL, 0x03, "Invalid CRC (not stripped?)" ) |
| #define EINVAL_EOF __einfo_error ( EINFO_EINVAL_EOF ) |
| #define EINFO_EINVAL_EOF \ |
| __einfo_uniqify ( EINFO_EINVAL, 0x04, "Invalid EoF delimiter" ) |
| |
| /** An FCoE port */ |
| struct fcoe_port { |
| /** Transport interface */ |
| struct interface transport; |
| /** Network device */ |
| struct net_device *netdev; |
| |
| /** Node WWN */ |
| union fcoe_name node_wwn; |
| /** Port WWN */ |
| union fcoe_name port_wwn; |
| |
| /** FIP retransmission timer */ |
| struct retry_timer timer; |
| /** FIP timeout counter */ |
| unsigned int timeouts; |
| /** Flags */ |
| unsigned int flags; |
| /** FCoE forwarder priority */ |
| unsigned int priority; |
| /** Keepalive delay (in ms) */ |
| unsigned int keepalive; |
| /** FCoE forwarder MAC address */ |
| uint8_t fcf_mac[ETH_ALEN]; |
| /** Local MAC address */ |
| uint8_t local_mac[ETH_ALEN]; |
| }; |
| |
| /** FCoE flags */ |
| enum fcoe_flags { |
| /** Underlying network device is available */ |
| FCOE_HAVE_NETWORK = 0x0001, |
| /** We have selected an FCoE forwarder to use */ |
| FCOE_HAVE_FCF = 0x0002, |
| /** We have a FIP-capable FCoE forwarder available to be used */ |
| FCOE_HAVE_FIP_FCF = 0x0004, |
| /** FCoE forwarder supports server-provided MAC addresses */ |
| FCOE_FCF_ALLOWS_SPMA = 0x0008, |
| /** An alternative VLAN has been found */ |
| FCOE_VLAN_FOUND = 0x0010, |
| /** VLAN discovery has timed out */ |
| FCOE_VLAN_TIMED_OUT = 0x0020, |
| }; |
| |
| struct net_driver fcoe_driver __net_driver; |
| struct net_protocol fcoe_protocol __net_protocol; |
| struct net_protocol fip_protocol __net_protocol; |
| |
| /** FCoE All-FCoE-MACs address */ |
| static uint8_t all_fcoe_macs[ETH_ALEN] = |
| { 0x01, 0x10, 0x18, 0x01, 0x00, 0x00 }; |
| |
| /** FCoE All-ENode-MACs address */ |
| static uint8_t all_enode_macs[ETH_ALEN] = |
| { 0x01, 0x10, 0x18, 0x01, 0x00, 0x01 }; |
| |
| /** FCoE All-FCF-MACs address */ |
| static uint8_t all_fcf_macs[ETH_ALEN] = |
| { 0x01, 0x10, 0x18, 0x01, 0x00, 0x02 }; |
| |
| /** Default FCoE forwarded MAC address */ |
| static uint8_t default_fcf_mac[ETH_ALEN] = |
| { 0x0e, 0xfc, 0x00, 0xff, 0xff, 0xfe }; |
| |
| /** Maximum number of VLAN requests before giving up on VLAN discovery */ |
| #define FCOE_MAX_VLAN_REQUESTS 2 |
| |
| /** Delay between retrying VLAN requests */ |
| #define FCOE_VLAN_RETRY_DELAY ( TICKS_PER_SEC ) |
| |
| /** Delay between retrying polling VLAN requests */ |
| #define FCOE_VLAN_POLL_DELAY ( 30 * TICKS_PER_SEC ) |
| |
| /** Maximum number of FIP solicitations before giving up on FIP */ |
| #define FCOE_MAX_FIP_SOLICITATIONS 2 |
| |
| /** Delay between retrying FIP solicitations */ |
| #define FCOE_FIP_RETRY_DELAY ( TICKS_PER_SEC ) |
| |
| /** Maximum number of missing discovery advertisements */ |
| #define FCOE_MAX_FIP_MISSING_KEEPALIVES 4 |
| |
| /****************************************************************************** |
| * |
| * FCoE protocol |
| * |
| ****************************************************************************** |
| */ |
| |
| /** |
| * Reset FCoE port |
| * |
| * @v fcoe FCoE port |
| */ |
| static void fcoe_reset ( struct fcoe_port *fcoe ) { |
| |
| /* Detach FC port, if any */ |
| intf_restart ( &fcoe->transport, -ECANCELED ); |
| |
| /* Reset any FIP state */ |
| stop_timer ( &fcoe->timer ); |
| fcoe->timeouts = 0; |
| fcoe->flags = 0; |
| fcoe->priority = ( FIP_LOWEST_PRIORITY + 1 ); |
| fcoe->keepalive = 0; |
| memcpy ( fcoe->fcf_mac, default_fcf_mac, |
| sizeof ( fcoe->fcf_mac ) ); |
| memcpy ( fcoe->local_mac, fcoe->netdev->ll_addr, |
| sizeof ( fcoe->local_mac ) ); |
| |
| /* Start FIP solicitation if network is available */ |
| if ( netdev_is_open ( fcoe->netdev ) && |
| netdev_link_ok ( fcoe->netdev ) ) { |
| fcoe->flags |= FCOE_HAVE_NETWORK; |
| start_timer_nodelay ( &fcoe->timer ); |
| DBGC ( fcoe, "FCoE %s starting %s\n", fcoe->netdev->name, |
| ( vlan_can_be_trunk ( fcoe->netdev ) ? |
| "VLAN discovery" : "FIP solicitation" ) ); |
| } |
| |
| /* Send notification of window change */ |
| xfer_window_changed ( &fcoe->transport ); |
| } |
| |
| /** |
| * Transmit FCoE packet |
| * |
| * @v fcoe FCoE port |
| * @v iobuf I/O buffer |
| * @v meta Data transfer metadata |
| * @ret rc Return status code |
| */ |
| static int fcoe_deliver ( struct fcoe_port *fcoe, |
| struct io_buffer *iobuf, |
| struct xfer_metadata *meta __unused ) { |
| struct fc_frame_header *fchdr = iobuf->data; |
| struct fc_els_frame_common *els = ( iobuf->data + sizeof ( *fchdr ) ); |
| struct fcoe_header *fcoehdr; |
| struct fcoe_footer *fcoeftr; |
| struct fip_header *fiphdr; |
| struct fip_login *fipflogi; |
| struct fip_mac_address *fipmac; |
| uint32_t crc; |
| struct net_protocol *net_protocol; |
| void *ll_source; |
| int rc; |
| |
| /* Send as FIP or FCoE as appropriate */ |
| if ( ( fchdr->r_ctl == ( FC_R_CTL_ELS | FC_R_CTL_UNSOL_CTRL ) ) && |
| ( els->command == FC_ELS_FLOGI ) && |
| ( fcoe->flags & FCOE_HAVE_FIP_FCF ) ) { |
| |
| /* Create FIP FLOGI descriptor */ |
| fipflogi = iob_push ( iobuf, |
| offsetof ( typeof ( *fipflogi ), fc ) ); |
| memset ( fipflogi, 0, offsetof ( typeof ( *fipflogi ), fc ) ); |
| fipflogi->type = FIP_FLOGI; |
| fipflogi->len = ( iob_len ( iobuf ) / 4 ); |
| |
| /* Create FIP MAC address descriptor */ |
| fipmac = iob_put ( iobuf, sizeof ( *fipmac ) ); |
| memset ( fipmac, 0, sizeof ( *fipmac ) ); |
| fipmac->type = FIP_MAC_ADDRESS; |
| fipmac->len = ( sizeof ( *fipmac ) / 4 ); |
| if ( fcoe->flags & FCOE_FCF_ALLOWS_SPMA ) { |
| memcpy ( fipmac->mac, fcoe->netdev->ll_addr, |
| sizeof ( fipmac->mac ) ); |
| } |
| |
| /* Create FIP header */ |
| fiphdr = iob_push ( iobuf, sizeof ( *fiphdr ) ); |
| memset ( fiphdr, 0, sizeof ( *fiphdr ) ); |
| fiphdr->version = FIP_VERSION; |
| fiphdr->code = htons ( FIP_CODE_ELS ); |
| fiphdr->subcode = FIP_ELS_REQUEST; |
| fiphdr->len = |
| htons ( ( iob_len ( iobuf ) - sizeof ( *fiphdr ) ) / 4); |
| fiphdr->flags = ( ( fcoe->flags & FCOE_FCF_ALLOWS_SPMA ) ? |
| htons ( FIP_SP ) : htons ( FIP_FP ) ); |
| |
| /* Send as FIP packet from netdev's own MAC address */ |
| net_protocol = &fip_protocol; |
| ll_source = fcoe->netdev->ll_addr; |
| |
| } else { |
| |
| /* Calculate CRC */ |
| crc = crc32_le ( ~((uint32_t)0), iobuf->data, |
| iob_len ( iobuf ) ); |
| |
| /* Create FCoE header */ |
| fcoehdr = iob_push ( iobuf, sizeof ( *fcoehdr ) ); |
| memset ( fcoehdr, 0, sizeof ( *fcoehdr ) ); |
| fcoehdr->sof = ( ( fchdr->seq_cnt == ntohs ( 0 ) ) ? |
| FCOE_SOF_I3 : FCOE_SOF_N3 ); |
| |
| /* Create FCoE footer */ |
| fcoeftr = iob_put ( iobuf, sizeof ( *fcoeftr ) ); |
| memset ( fcoeftr, 0, sizeof ( *fcoeftr ) ); |
| fcoeftr->crc = cpu_to_le32 ( crc ^ ~((uint32_t)0) ); |
| fcoeftr->eof = ( ( fchdr->f_ctl_es & FC_F_CTL_ES_END ) ? |
| FCOE_EOF_T : FCOE_EOF_N ); |
| |
| /* Send as FCoE packet from FCoE MAC address */ |
| net_protocol = &fcoe_protocol; |
| ll_source = fcoe->local_mac; |
| } |
| |
| /* Transmit packet */ |
| if ( ( rc = net_tx ( iob_disown ( iobuf ), fcoe->netdev, net_protocol, |
| fcoe->fcf_mac, ll_source ) ) != 0 ) { |
| DBGC ( fcoe, "FCoE %s could not transmit: %s\n", |
| fcoe->netdev->name, strerror ( rc ) ); |
| goto done; |
| } |
| |
| done: |
| free_iob ( iobuf ); |
| return rc; |
| } |
| |
| /** |
| * Allocate FCoE I/O buffer |
| * |
| * @v len Payload length |
| * @ret iobuf I/O buffer, or NULL |
| */ |
| static struct io_buffer * fcoe_alloc_iob ( struct fcoe_port *fcoe __unused, |
| size_t len ) { |
| struct io_buffer *iobuf; |
| |
| iobuf = alloc_iob ( MAX_LL_HEADER_LEN + sizeof ( struct fcoe_header ) + |
| len + sizeof ( struct fcoe_footer ) ); |
| if ( iobuf ) { |
| iob_reserve ( iobuf, ( MAX_LL_HEADER_LEN + |
| sizeof ( struct fcoe_header ) ) ); |
| } |
| return iobuf; |
| } |
| |
| /** |
| * Process incoming FCoE packets |
| * |
| * @v iobuf I/O buffer |
| * @v netdev Network device |
| * @v ll_dest Link-layer destination address |
| * @v ll_source Link-layer source address |
| * @v flags Packet flags |
| * @ret rc Return status code |
| */ |
| static int fcoe_rx ( struct io_buffer *iobuf, struct net_device *netdev, |
| const void *ll_dest, const void *ll_source, |
| unsigned int flags __unused ) { |
| struct fcoe_header *fcoehdr; |
| struct fcoe_footer *fcoeftr; |
| struct fcoe_port *fcoe; |
| int rc; |
| |
| /* Identify FCoE port */ |
| fcoe = netdev_priv ( netdev, &fcoe_driver ); |
| if ( ! fcoe->netdev ) { |
| DBG ( "FCoE received frame for net device %s missing FCoE " |
| "port\n", netdev->name ); |
| rc = -ENOTCONN; |
| goto done; |
| } |
| |
| /* Discard packets not destined for us */ |
| if ( ( memcmp ( fcoe->local_mac, ll_dest, |
| sizeof ( fcoe->local_mac ) ) != 0 ) && |
| ( memcmp ( default_fcf_mac, ll_dest, |
| sizeof ( default_fcf_mac ) ) != 0 ) ) { |
| DBGC2 ( fcoe, "FCoE %s ignoring packet for %s\n", |
| fcoe->netdev->name, eth_ntoa ( ll_dest ) ); |
| rc = -ENOTCONN; |
| goto done; |
| } |
| |
| /* Sanity check */ |
| if ( iob_len ( iobuf ) < ( sizeof ( *fcoehdr ) + sizeof ( *fcoeftr ) )){ |
| DBGC ( fcoe, "FCoE %s received under-length frame (%zd " |
| "bytes)\n", fcoe->netdev->name, iob_len ( iobuf ) ); |
| rc = -EINVAL_UNDERLENGTH; |
| goto done; |
| } |
| |
| /* Strip header and footer */ |
| fcoehdr = iobuf->data; |
| iob_pull ( iobuf, sizeof ( *fcoehdr ) ); |
| fcoeftr = ( iobuf->data + iob_len ( iobuf ) - sizeof ( *fcoeftr ) ); |
| iob_unput ( iobuf, sizeof ( *fcoeftr ) ); |
| |
| /* Validity checks */ |
| if ( fcoehdr->version != FCOE_FRAME_VER ) { |
| DBGC ( fcoe, "FCoE %s received unsupported frame version " |
| "%02x\n", fcoe->netdev->name, fcoehdr->version ); |
| rc = -EPROTONOSUPPORT; |
| goto done; |
| } |
| if ( ! ( ( fcoehdr->sof == FCOE_SOF_I3 ) || |
| ( fcoehdr->sof == FCOE_SOF_N3 ) ) ) { |
| DBGC ( fcoe, "FCoE %s received unsupported start-of-frame " |
| "delimiter %02x\n", fcoe->netdev->name, fcoehdr->sof ); |
| rc = -EINVAL_SOF; |
| goto done; |
| } |
| if ( ( le32_to_cpu ( fcoeftr->crc ) ^ ~((uint32_t)0) ) != |
| crc32_le ( ~((uint32_t)0), iobuf->data, iob_len ( iobuf ) ) ) { |
| DBGC ( fcoe, "FCoE %s received invalid CRC\n", |
| fcoe->netdev->name ); |
| rc = -EINVAL_CRC; |
| goto done; |
| } |
| if ( ! ( ( fcoeftr->eof == FCOE_EOF_N ) || |
| ( fcoeftr->eof == FCOE_EOF_T ) ) ) { |
| DBGC ( fcoe, "FCoE %s received unsupported end-of-frame " |
| "delimiter %02x\n", fcoe->netdev->name, fcoeftr->eof ); |
| rc = -EINVAL_EOF; |
| goto done; |
| } |
| |
| /* Record FCF address if applicable */ |
| if ( ( fcoe->flags & FCOE_HAVE_FCF ) && |
| ( ! ( fcoe->flags & FCOE_HAVE_FIP_FCF ) ) ) { |
| memcpy ( &fcoe->fcf_mac, ll_source, sizeof ( fcoe->fcf_mac ) ); |
| } |
| |
| /* Hand off via transport interface */ |
| if ( ( rc = xfer_deliver_iob ( &fcoe->transport, |
| iob_disown ( iobuf ) ) ) != 0 ) { |
| DBGC ( fcoe, "FCoE %s could not deliver frame: %s\n", |
| fcoe->netdev->name, strerror ( rc ) ); |
| goto done; |
| } |
| |
| done: |
| free_iob ( iobuf ); |
| return rc; |
| } |
| |
| /** |
| * Check FCoE flow control window |
| * |
| * @v fcoe FCoE port |
| * @ret len Length of window |
| */ |
| static size_t fcoe_window ( struct fcoe_port *fcoe ) { |
| return ( ( fcoe->flags & FCOE_HAVE_FCF ) ? ~( ( size_t ) 0 ) : 0 ); |
| } |
| |
| /** |
| * Close FCoE port |
| * |
| * @v fcoe FCoE port |
| * @v rc Reason for close |
| */ |
| static void fcoe_close ( struct fcoe_port *fcoe, int rc ) { |
| |
| stop_timer ( &fcoe->timer ); |
| intf_shutdown ( &fcoe->transport, rc ); |
| } |
| |
| /** |
| * Identify device underlying FCoE port |
| * |
| * @v fcoe FCoE port |
| * @ret device Underlying device |
| */ |
| static struct device * fcoe_identify_device ( struct fcoe_port *fcoe ) { |
| return fcoe->netdev->dev; |
| } |
| |
| /** FCoE transport interface operations */ |
| static struct interface_operation fcoe_transport_op[] = { |
| INTF_OP ( xfer_deliver, struct fcoe_port *, fcoe_deliver ), |
| INTF_OP ( xfer_alloc_iob, struct fcoe_port *, fcoe_alloc_iob ), |
| INTF_OP ( xfer_window, struct fcoe_port *, fcoe_window ), |
| INTF_OP ( intf_close, struct fcoe_port *, fcoe_close ), |
| INTF_OP ( identify_device, struct fcoe_port *, |
| fcoe_identify_device ), |
| }; |
| |
| /** FCoE transport interface descriptor */ |
| static struct interface_descriptor fcoe_transport_desc = |
| INTF_DESC ( struct fcoe_port, transport, fcoe_transport_op ); |
| |
| /****************************************************************************** |
| * |
| * FIP protocol |
| * |
| ****************************************************************************** |
| */ |
| |
| /** |
| * Parse FIP packet into descriptor set |
| * |
| * @v fcoe FCoE port |
| * @v fiphdr FIP header |
| * @v len Length of FIP packet |
| * @v descs Descriptor set to fill in |
| * @ret rc Return status code |
| */ |
| static int fcoe_fip_parse ( struct fcoe_port *fcoe, struct fip_header *fiphdr, |
| size_t len, struct fip_descriptors *descs ) { |
| union fip_descriptor *desc; |
| size_t descs_len; |
| size_t desc_len; |
| size_t desc_offset; |
| unsigned int desc_type; |
| |
| /* Check FIP version */ |
| if ( fiphdr->version != FIP_VERSION ) { |
| DBGC ( fcoe, "FCoE %s received unsupported FIP version %02x\n", |
| fcoe->netdev->name, fiphdr->version ); |
| return -EINVAL; |
| } |
| |
| /* Check length */ |
| descs_len = ( ntohs ( fiphdr->len ) * 4 ); |
| if ( ( sizeof ( *fiphdr ) + descs_len ) > len ) { |
| DBGC ( fcoe, "FCoE %s received bad descriptor list length\n", |
| fcoe->netdev->name ); |
| return -EINVAL; |
| } |
| |
| /* Parse descriptor list */ |
| memset ( descs, 0, sizeof ( *descs ) ); |
| for ( desc_offset = 0 ; |
| desc_offset <= ( descs_len - sizeof ( desc->common ) ) ; |
| desc_offset += desc_len ) { |
| |
| /* Find descriptor and validate length */ |
| desc = ( ( ( void * ) ( fiphdr + 1 ) ) + desc_offset ); |
| desc_type = desc->common.type; |
| desc_len = ( desc->common.len * 4 ); |
| if ( desc_len == 0 ) { |
| DBGC ( fcoe, "FCoE %s received zero-length " |
| "descriptor\n", fcoe->netdev->name ); |
| return -EINVAL; |
| } |
| if ( ( desc_offset + desc_len ) > descs_len ) { |
| DBGC ( fcoe, "FCoE %s descriptor overrun\n", |
| fcoe->netdev->name ); |
| return -EINVAL; |
| } |
| |
| /* Handle descriptors that we understand */ |
| if ( ( desc_type > FIP_RESERVED ) && |
| ( desc_type < FIP_NUM_DESCRIPTOR_TYPES ) ) { |
| /* Use only the first instance of a descriptor */ |
| if ( descs->desc[desc_type] == NULL ) |
| descs->desc[desc_type] = desc; |
| continue; |
| } |
| |
| /* Abort if we cannot understand a critical descriptor */ |
| if ( FIP_IS_CRITICAL ( desc_type ) ) { |
| DBGC ( fcoe, "FCoE %s cannot understand critical " |
| "descriptor type %02x\n", |
| fcoe->netdev->name, desc_type ); |
| return -ENOTSUP; |
| } |
| |
| /* Ignore non-critical descriptors that we cannot understand */ |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Send FIP VLAN request |
| * |
| * @v fcoe FCoE port |
| * @ret rc Return status code |
| */ |
| static int fcoe_fip_tx_vlan ( struct fcoe_port *fcoe ) { |
| struct io_buffer *iobuf; |
| struct { |
| struct fip_header hdr; |
| struct fip_mac_address mac_address; |
| } __attribute__ (( packed )) *request; |
| int rc; |
| |
| /* Allocate I/O buffer */ |
| iobuf = alloc_iob ( MAX_LL_HEADER_LEN + sizeof ( *request ) ); |
| if ( ! iobuf ) |
| return -ENOMEM; |
| iob_reserve ( iobuf, MAX_LL_HEADER_LEN ); |
| |
| /* Construct VLAN request */ |
| request = iob_put ( iobuf, sizeof ( *request ) ); |
| memset ( request, 0, sizeof ( *request ) ); |
| request->hdr.version = FIP_VERSION; |
| request->hdr.code = htons ( FIP_CODE_VLAN ); |
| request->hdr.subcode = FIP_VLAN_REQUEST; |
| request->hdr.len = htons ( ( sizeof ( *request ) - |
| sizeof ( request->hdr ) ) / 4 ); |
| request->mac_address.type = FIP_MAC_ADDRESS; |
| request->mac_address.len = |
| ( sizeof ( request->mac_address ) / 4 ); |
| memcpy ( request->mac_address.mac, fcoe->netdev->ll_addr, |
| sizeof ( request->mac_address.mac ) ); |
| |
| /* Send VLAN request */ |
| if ( ( rc = net_tx ( iob_disown ( iobuf ), fcoe->netdev, |
| &fip_protocol, all_fcf_macs, |
| fcoe->netdev->ll_addr ) ) != 0 ) { |
| DBGC ( fcoe, "FCoE %s could not send VLAN request: " |
| "%s\n", fcoe->netdev->name, strerror ( rc ) ); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Handle received FIP VLAN notification |
| * |
| * @v fcoe FCoE port |
| * @v descs Descriptor list |
| * @v flags Flags |
| * @ret rc Return status code |
| */ |
| static int fcoe_fip_rx_vlan ( struct fcoe_port *fcoe, |
| struct fip_descriptors *descs, |
| unsigned int flags __unused ) { |
| struct fip_mac_address *mac_address = fip_mac_address ( descs ); |
| struct fip_vlan *vlan = fip_vlan ( descs ); |
| unsigned int tag; |
| int rc; |
| |
| /* Sanity checks */ |
| if ( ! mac_address ) { |
| DBGC ( fcoe, "FCoE %s received VLAN notification missing MAC " |
| "address\n", fcoe->netdev->name ); |
| return -EINVAL; |
| } |
| if ( ! vlan ) { |
| DBGC ( fcoe, "FCoE %s received VLAN notification missing VLAN " |
| "tag\n", fcoe->netdev->name ); |
| return -EINVAL; |
| } |
| |
| /* Create VLAN */ |
| tag = ntohs ( vlan->vlan ); |
| DBGC ( fcoe, "FCoE %s creating VLAN %d for FCF %s\n", |
| fcoe->netdev->name, tag, eth_ntoa ( mac_address->mac ) ); |
| if ( ( rc = vlan_create ( fcoe->netdev, tag, |
| FCOE_VLAN_PRIORITY ) ) != 0 ) { |
| DBGC ( fcoe, "FCoE %s could not create VLAN %d: %s\n", |
| fcoe->netdev->name, tag, strerror ( rc ) ); |
| return rc; |
| } |
| |
| /* Record that a VLAN was found. This FCoE port will play no |
| * further active role; the real FCoE traffic will use the |
| * port automatically created for the new VLAN device. |
| */ |
| fcoe->flags |= FCOE_VLAN_FOUND; |
| |
| return 0; |
| } |
| |
| /** |
| * Send FIP discovery solicitation |
| * |
| * @v fcoe FCoE port |
| * @ret rc Return status code |
| */ |
| static int fcoe_fip_tx_solicitation ( struct fcoe_port *fcoe ) { |
| struct io_buffer *iobuf; |
| struct { |
| struct fip_header hdr; |
| struct fip_mac_address mac_address; |
| struct fip_name_id name_id; |
| struct fip_max_fcoe_size max_fcoe_size; |
| } __attribute__ (( packed )) *solicitation; |
| int rc; |
| |
| /* Allocate I/O buffer */ |
| iobuf = alloc_iob ( MAX_LL_HEADER_LEN + sizeof ( *solicitation ) ); |
| if ( ! iobuf ) |
| return -ENOMEM; |
| iob_reserve ( iobuf, MAX_LL_HEADER_LEN ); |
| |
| /* Construct discovery solicitation */ |
| solicitation = iob_put ( iobuf, sizeof ( *solicitation ) ); |
| memset ( solicitation, 0, sizeof ( *solicitation ) ); |
| solicitation->hdr.version = FIP_VERSION; |
| solicitation->hdr.code = htons ( FIP_CODE_DISCOVERY ); |
| solicitation->hdr.subcode = FIP_DISCOVERY_SOLICIT; |
| solicitation->hdr.len = htons ( ( sizeof ( *solicitation ) - |
| sizeof ( solicitation->hdr ) ) / 4 ); |
| solicitation->hdr.flags = htons ( FIP_FP | FIP_SP ); |
| solicitation->mac_address.type = FIP_MAC_ADDRESS; |
| solicitation->mac_address.len = |
| ( sizeof ( solicitation->mac_address ) / 4 ); |
| memcpy ( solicitation->mac_address.mac, fcoe->netdev->ll_addr, |
| sizeof ( solicitation->mac_address.mac ) ); |
| solicitation->name_id.type = FIP_NAME_ID; |
| solicitation->name_id.len = ( sizeof ( solicitation->name_id ) / 4 ); |
| memcpy ( &solicitation->name_id.name, &fcoe->node_wwn.fc, |
| sizeof ( solicitation->name_id.name ) ); |
| solicitation->max_fcoe_size.type = FIP_MAX_FCOE_SIZE; |
| solicitation->max_fcoe_size.len = |
| ( sizeof ( solicitation->max_fcoe_size ) / 4 ); |
| solicitation->max_fcoe_size.mtu = |
| htons ( ETH_MAX_MTU - sizeof ( struct fcoe_header ) - |
| sizeof ( struct fcoe_footer ) ); |
| |
| /* Send discovery solicitation */ |
| if ( ( rc = net_tx ( iob_disown ( iobuf ), fcoe->netdev, |
| &fip_protocol, all_fcf_macs, |
| fcoe->netdev->ll_addr ) ) != 0 ) { |
| DBGC ( fcoe, "FCoE %s could not send discovery solicitation: " |
| "%s\n", fcoe->netdev->name, strerror ( rc ) ); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Handle received FIP discovery advertisement |
| * |
| * @v fcoe FCoE port |
| * @v descs Descriptor list |
| * @v flags Flags |
| * @ret rc Return status code |
| */ |
| static int fcoe_fip_rx_advertisement ( struct fcoe_port *fcoe, |
| struct fip_descriptors *descs, |
| unsigned int flags ) { |
| struct fip_priority *priority = fip_priority ( descs ); |
| struct fip_mac_address *mac_address = fip_mac_address ( descs ); |
| struct fip_fka_adv_p *fka_adv_p = fip_fka_adv_p ( descs ); |
| |
| /* Sanity checks */ |
| if ( ! priority ) { |
| DBGC ( fcoe, "FCoE %s received advertisement missing " |
| "priority\n", fcoe->netdev->name ); |
| return -EINVAL; |
| } |
| if ( ! mac_address ) { |
| DBGC ( fcoe, "FCoE %s received advertisement missing MAC " |
| "address\n", fcoe->netdev->name ); |
| return -EINVAL; |
| } |
| if ( ! fka_adv_p ) { |
| DBGC ( fcoe, "FCoE %s received advertisement missing FKA ADV " |
| "period\n", fcoe->netdev->name ); |
| return -EINVAL; |
| } |
| |
| if ( ! ( fcoe->flags & FCOE_HAVE_FCF ) ) { |
| |
| /* We are soliciting for an FCF. Store the highest |
| * (i.e. lowest-valued) priority solicited |
| * advertisement that we receive. |
| */ |
| if ( ( ( flags & ( FIP_A | FIP_S | FIP_F ) ) == |
| ( FIP_A | FIP_S | FIP_F ) ) && |
| ( priority->priority < fcoe->priority ) ) { |
| |
| fcoe->flags |= FCOE_HAVE_FIP_FCF; |
| fcoe->priority = priority->priority; |
| if ( fka_adv_p->flags & FIP_NO_KEEPALIVE ) { |
| fcoe->keepalive = 0; |
| } else { |
| fcoe->keepalive = ntohl ( fka_adv_p->period ); |
| } |
| fcoe->flags &= ~FCOE_FCF_ALLOWS_SPMA; |
| if ( flags & FIP_SP ) |
| fcoe->flags |= FCOE_FCF_ALLOWS_SPMA; |
| memcpy ( fcoe->fcf_mac, mac_address->mac, |
| sizeof ( fcoe->fcf_mac ) ); |
| DBGC ( fcoe, "FCoE %s selected FCF %s (pri %d", |
| fcoe->netdev->name, eth_ntoa ( fcoe->fcf_mac ), |
| fcoe->priority ); |
| if ( fcoe->keepalive ) { |
| DBGC ( fcoe, ", FKA ADV %dms", |
| fcoe->keepalive ); |
| } |
| DBGC ( fcoe, ", %cPMA)\n", |
| ( ( fcoe->flags & FCOE_FCF_ALLOWS_SPMA ) ? |
| 'S' : 'F' ) ); |
| } |
| |
| } else if ( fcoe->flags & FCOE_HAVE_FIP_FCF ) { |
| |
| /* We are checking that the FCF remains alive. Reset |
| * the timeout counter if this is an advertisement |
| * from our forwarder. |
| */ |
| if ( memcmp ( fcoe->fcf_mac, mac_address->mac, |
| sizeof ( fcoe->fcf_mac ) ) == 0 ) { |
| fcoe->timeouts = 0; |
| } |
| |
| } else { |
| |
| /* We are operating in non-FIP mode and have received |
| * a FIP advertisement. Reset the link in order to |
| * attempt FIP. |
| */ |
| fcoe_reset ( fcoe ); |
| |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Handle received FIP ELS response |
| * |
| * @v fcoe FCoE port |
| * @v descs Descriptor list |
| * @v flags Flags |
| * @ret rc Return status code |
| */ |
| static int fcoe_fip_rx_els_response ( struct fcoe_port *fcoe, |
| struct fip_descriptors *descs, |
| unsigned int flags __unused ) { |
| struct fip_els *flogi = fip_flogi ( descs ); |
| struct fip_mac_address *mac_address = fip_mac_address ( descs ); |
| void *frame; |
| size_t frame_len; |
| int rc; |
| |
| /* Sanity checks */ |
| if ( ! flogi ) { |
| DBGC ( fcoe, "FCoE %s received ELS response missing FLOGI\n", |
| fcoe->netdev->name ); |
| return -EINVAL; |
| } |
| if ( ! mac_address ) { |
| DBGC ( fcoe, "FCoE %s received ELS response missing MAC " |
| "address\n", fcoe->netdev->name ); |
| return -EINVAL; |
| } |
| |
| /* Record local MAC address */ |
| memcpy ( fcoe->local_mac, mac_address->mac, sizeof ( fcoe->local_mac )); |
| DBGC ( fcoe, "FCoE %s using local MAC %s\n", |
| fcoe->netdev->name, eth_ntoa ( fcoe->local_mac ) ); |
| |
| /* Hand off via transport interface */ |
| frame = &flogi->fc; |
| frame_len = ( ( flogi->len * 4 ) - offsetof ( typeof ( *flogi ), fc ) ); |
| if ( ( rc = xfer_deliver_raw ( &fcoe->transport, frame, |
| frame_len ) ) != 0 ) { |
| DBGC ( fcoe, "FCoE %s could not deliver FIP FLOGI frame: %s\n", |
| fcoe->netdev->name, strerror ( rc ) ); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Send FIP keepalive |
| * |
| * @v fcoe FCoE port |
| * @ret rc Return status code |
| */ |
| static int fcoe_fip_tx_keepalive ( struct fcoe_port *fcoe ) { |
| struct io_buffer *iobuf; |
| struct { |
| struct fip_header hdr; |
| struct fip_mac_address mac_address; |
| } __attribute__ (( packed )) *keepalive; |
| int rc; |
| |
| /* Allocate I/O buffer */ |
| iobuf = alloc_iob ( MAX_LL_HEADER_LEN + sizeof ( *keepalive ) ); |
| if ( ! iobuf ) |
| return -ENOMEM; |
| iob_reserve ( iobuf, MAX_LL_HEADER_LEN ); |
| |
| /* Construct keepalive */ |
| keepalive = iob_put ( iobuf, sizeof ( *keepalive ) ); |
| memset ( keepalive, 0, sizeof ( *keepalive ) ); |
| keepalive->hdr.version = FIP_VERSION; |
| keepalive->hdr.code = htons ( FIP_CODE_MAINTAIN ); |
| keepalive->hdr.subcode = FIP_MAINTAIN_KEEP_ALIVE; |
| keepalive->hdr.len = htons ( ( sizeof ( *keepalive ) - |
| sizeof ( keepalive->hdr ) ) / 4 ); |
| keepalive->mac_address.type = FIP_MAC_ADDRESS; |
| keepalive->mac_address.len = |
| ( sizeof ( keepalive->mac_address ) / 4 ); |
| memcpy ( keepalive->mac_address.mac, fcoe->netdev->ll_addr, |
| sizeof ( keepalive->mac_address.mac ) ); |
| |
| /* Send keepalive */ |
| if ( ( rc = net_tx ( iob_disown ( iobuf ), fcoe->netdev, |
| &fip_protocol, fcoe->fcf_mac, |
| fcoe->netdev->ll_addr ) ) != 0 ) { |
| DBGC ( fcoe, "FCoE %s could not send keepalive: %s\n", |
| fcoe->netdev->name, strerror ( rc ) ); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** A FIP handler */ |
| struct fip_handler { |
| /** Protocol code */ |
| uint16_t code; |
| /** Protocol subcode */ |
| uint8_t subcode; |
| /** |
| * Receive FIP packet |
| * |
| * @v fcoe FCoE port |
| * @v descs Descriptor list |
| * @v flags Flags |
| * @ret rc Return status code |
| */ |
| int ( * rx ) ( struct fcoe_port *fcoe, struct fip_descriptors *descs, |
| unsigned int flags ); |
| }; |
| |
| /** FIP handlers */ |
| static struct fip_handler fip_handlers[] = { |
| { FIP_CODE_VLAN, FIP_VLAN_NOTIFY, |
| fcoe_fip_rx_vlan }, |
| { FIP_CODE_DISCOVERY, FIP_DISCOVERY_ADVERTISE, |
| fcoe_fip_rx_advertisement }, |
| { FIP_CODE_ELS, FIP_ELS_RESPONSE, |
| fcoe_fip_rx_els_response }, |
| }; |
| |
| /** |
| * Process incoming FIP packets |
| * |
| * @v iobuf I/O buffer |
| * @v netdev Network device |
| * @v ll_dest Link-layer destination address |
| * @v ll_source Link-layer source address |
| * @v flags Packet flags |
| * @ret rc Return status code |
| */ |
| static int fcoe_fip_rx ( struct io_buffer *iobuf, |
| struct net_device *netdev, |
| const void *ll_dest, |
| const void *ll_source __unused, |
| unsigned int flags __unused ) { |
| struct fip_header *fiphdr = iobuf->data; |
| struct fip_descriptors descs; |
| struct fip_handler *handler; |
| struct fcoe_port *fcoe; |
| unsigned int i; |
| int rc; |
| |
| /* Identify FCoE port */ |
| fcoe = netdev_priv ( netdev, &fcoe_driver ); |
| if ( ! fcoe->netdev ) { |
| DBG ( "FCoE received FIP frame for net device %s missing FCoE " |
| "port\n", netdev->name ); |
| rc = -ENOTCONN; |
| goto done; |
| } |
| |
| /* Discard packets not destined for us */ |
| if ( ( memcmp ( fcoe->netdev->ll_addr, ll_dest, ETH_ALEN ) != 0 ) && |
| ( memcmp ( all_fcoe_macs, ll_dest, |
| sizeof ( all_fcoe_macs ) ) != 0 ) && |
| ( memcmp ( all_enode_macs, ll_dest, |
| sizeof ( all_enode_macs ) ) != 0 ) ) { |
| DBGC2 ( fcoe, "FCoE %s ignoring FIP packet for %s\n", |
| fcoe->netdev->name, eth_ntoa ( ll_dest ) ); |
| rc = -ENOTCONN; |
| goto done; |
| } |
| |
| /* Parse FIP packet */ |
| if ( ( rc = fcoe_fip_parse ( fcoe, fiphdr, iob_len ( iobuf ), |
| &descs ) ) != 0 ) |
| goto done; |
| |
| /* Find a suitable handler */ |
| for ( i = 0 ; i < ( sizeof ( fip_handlers ) / |
| sizeof ( fip_handlers[0] ) ) ; i++ ) { |
| handler = &fip_handlers[i]; |
| if ( ( handler->code == ntohs ( fiphdr->code ) ) && |
| ( handler->subcode == fiphdr->subcode ) ) { |
| rc = handler->rx ( fcoe, &descs, |
| ntohs ( fiphdr->flags ) ); |
| goto done; |
| } |
| } |
| DBGC ( fcoe, "FCoE %s received unsupported FIP code %04x.%02x\n", |
| fcoe->netdev->name, ntohs ( fiphdr->code ), fiphdr->subcode ); |
| rc = -ENOTSUP; |
| |
| done: |
| free_iob ( iobuf ); |
| return rc; |
| } |
| |
| /****************************************************************************** |
| * |
| * FCoE ports |
| * |
| ****************************************************************************** |
| */ |
| |
| /** |
| * Handle FCoE timer expiry |
| * |
| * @v timer FIP timer |
| * @v over Timer expired |
| */ |
| static void fcoe_expired ( struct retry_timer *timer, int over __unused ) { |
| struct fcoe_port *fcoe = |
| container_of ( timer, struct fcoe_port, timer ); |
| int rc; |
| |
| /* Sanity check */ |
| assert ( fcoe->flags & FCOE_HAVE_NETWORK ); |
| |
| /* Increment the timeout counter */ |
| fcoe->timeouts++; |
| |
| if ( vlan_can_be_trunk ( fcoe->netdev ) && |
| ! ( fcoe->flags & FCOE_VLAN_TIMED_OUT ) ) { |
| |
| /* If we have already found a VLAN, send infrequent |
| * VLAN requests, in case VLAN information changes. |
| */ |
| if ( fcoe->flags & FCOE_VLAN_FOUND ) { |
| fcoe->flags &= ~FCOE_VLAN_FOUND; |
| fcoe->timeouts = 0; |
| start_timer_fixed ( &fcoe->timer, |
| FCOE_VLAN_POLL_DELAY ); |
| fcoe_fip_tx_vlan ( fcoe ); |
| return; |
| } |
| |
| /* If we have not yet found a VLAN, and we have not |
| * yet timed out and given up on finding one, then |
| * send a VLAN request and wait. |
| */ |
| if ( fcoe->timeouts <= FCOE_MAX_VLAN_REQUESTS ) { |
| start_timer_fixed ( &fcoe->timer, |
| FCOE_VLAN_RETRY_DELAY ); |
| fcoe_fip_tx_vlan ( fcoe ); |
| return; |
| } |
| |
| /* We have timed out waiting for a VLAN; proceed to |
| * FIP discovery. |
| */ |
| fcoe->flags |= FCOE_VLAN_TIMED_OUT; |
| fcoe->timeouts = 0; |
| DBGC ( fcoe, "FCoE %s giving up on VLAN discovery\n", |
| fcoe->netdev->name ); |
| start_timer_nodelay ( &fcoe->timer ); |
| |
| } else if ( ! ( fcoe->flags & FCOE_HAVE_FCF ) ) { |
| |
| /* If we have not yet found a FIP-capable forwarder, |
| * and we have not yet timed out and given up on |
| * finding one, then send a FIP solicitation and wait. |
| */ |
| start_timer_fixed ( &fcoe->timer, FCOE_FIP_RETRY_DELAY ); |
| if ( ( ! ( fcoe->flags & FCOE_HAVE_FIP_FCF ) ) && |
| ( fcoe->timeouts <= FCOE_MAX_FIP_SOLICITATIONS ) ) { |
| fcoe_fip_tx_solicitation ( fcoe ); |
| return; |
| } |
| |
| /* Attach Fibre Channel port */ |
| if ( ( rc = fc_port_open ( &fcoe->transport, &fcoe->node_wwn.fc, |
| &fcoe->port_wwn.fc, |
| fcoe->netdev->name ) ) != 0 ) { |
| DBGC ( fcoe, "FCoE %s could not create FC port: %s\n", |
| fcoe->netdev->name, strerror ( rc ) ); |
| /* We will try again on the next timer expiry */ |
| return; |
| } |
| stop_timer ( &fcoe->timer ); |
| |
| /* Either we have found a FIP-capable forwarder, or we |
| * have timed out and will fall back to pre-FIP mode. |
| */ |
| fcoe->flags |= FCOE_HAVE_FCF; |
| fcoe->timeouts = 0; |
| DBGC ( fcoe, "FCoE %s using %sFIP FCF %s\n", fcoe->netdev->name, |
| ( ( fcoe->flags & FCOE_HAVE_FIP_FCF ) ? "" : "non-" ), |
| eth_ntoa ( fcoe->fcf_mac ) ); |
| |
| /* Start sending keepalives if applicable */ |
| if ( fcoe->keepalive ) |
| start_timer_nodelay ( &fcoe->timer ); |
| |
| /* Send notification of window change */ |
| xfer_window_changed ( &fcoe->transport ); |
| |
| } else { |
| |
| /* Send keepalive */ |
| start_timer_fixed ( &fcoe->timer, |
| ( fcoe->keepalive * TICKS_PER_MS ) ); |
| fcoe_fip_tx_keepalive ( fcoe ); |
| |
| /* Abandon FCF if we have not seen its advertisements */ |
| if ( fcoe->timeouts > FCOE_MAX_FIP_MISSING_KEEPALIVES ) { |
| DBGC ( fcoe, "FCoE %s abandoning FCF %s\n", |
| fcoe->netdev->name, eth_ntoa ( fcoe->fcf_mac )); |
| fcoe_reset ( fcoe ); |
| } |
| } |
| } |
| |
| /** |
| * Create FCoE port |
| * |
| * @v netdev Network device |
| * @v priv Private data |
| * @ret rc Return status code |
| */ |
| static int fcoe_probe ( struct net_device *netdev, void *priv ) { |
| struct ll_protocol *ll_protocol = netdev->ll_protocol; |
| struct fcoe_port *fcoe = priv; |
| |
| /* Sanity check */ |
| if ( ll_protocol->ll_proto != htons ( ARPHRD_ETHER ) ) { |
| /* Not an error; simply skip this net device */ |
| DBG ( "FCoE skipping non-Ethernet device %s\n", netdev->name ); |
| return 0; |
| } |
| |
| /* Initialise structure */ |
| intf_init ( &fcoe->transport, &fcoe_transport_desc, &netdev->refcnt ); |
| timer_init ( &fcoe->timer, fcoe_expired, &netdev->refcnt ); |
| fcoe->netdev = netdev; |
| |
| /* Construct node and port names */ |
| fcoe->node_wwn.fcoe.authority = htons ( FCOE_AUTHORITY_IEEE ); |
| memcpy ( &fcoe->node_wwn.fcoe.mac, netdev->ll_addr, |
| sizeof ( fcoe->node_wwn.fcoe.mac ) ); |
| fcoe->port_wwn.fcoe.authority = htons ( FCOE_AUTHORITY_IEEE_EXTENDED ); |
| memcpy ( &fcoe->port_wwn.fcoe.mac, netdev->ll_addr, |
| sizeof ( fcoe->port_wwn.fcoe.mac ) ); |
| |
| DBGC ( fcoe, "FCoE %s is %s", fcoe->netdev->name, |
| fc_ntoa ( &fcoe->node_wwn.fc ) ); |
| DBGC ( fcoe, " port %s\n", fc_ntoa ( &fcoe->port_wwn.fc ) ); |
| |
| return 0; |
| } |
| |
| /** |
| * Handle FCoE port device or link state change |
| * |
| * @v netdev Network device |
| * @v priv Private data |
| */ |
| static void fcoe_notify ( struct net_device *netdev, void *priv ) { |
| struct fcoe_port *fcoe = priv; |
| |
| /* Skip non-FCoE net devices */ |
| if ( ! fcoe->netdev ) |
| return; |
| |
| /* Reset the FCoE link if necessary */ |
| if ( ! ( netdev_is_open ( netdev ) && |
| netdev_link_ok ( netdev ) && |
| ( fcoe->flags & FCOE_HAVE_NETWORK ) ) ) { |
| fcoe_reset ( fcoe ); |
| } |
| } |
| |
| /** |
| * Destroy FCoE port |
| * |
| * @v netdev Network device |
| * @v priv Private data |
| */ |
| static void fcoe_remove ( struct net_device *netdev __unused, void *priv ) { |
| struct fcoe_port *fcoe = priv; |
| |
| /* Skip non-FCoE net devices */ |
| if ( ! fcoe->netdev ) |
| return; |
| |
| /* Close FCoE device */ |
| fcoe_close ( fcoe, 0 ); |
| } |
| |
| /** FCoE driver */ |
| struct net_driver fcoe_driver __net_driver = { |
| .name = "FCoE", |
| .priv_len = sizeof ( struct fcoe_port ), |
| .probe = fcoe_probe, |
| .notify = fcoe_notify, |
| .remove = fcoe_remove, |
| }; |
| |
| /** FCoE protocol */ |
| struct net_protocol fcoe_protocol __net_protocol = { |
| .name = "FCoE", |
| .net_proto = htons ( ETH_P_FCOE ), |
| .rx = fcoe_rx, |
| }; |
| |
| /** FIP protocol */ |
| struct net_protocol fip_protocol __net_protocol = { |
| .name = "FIP", |
| .net_proto = htons ( ETH_P_FIP ), |
| .rx = fcoe_fip_rx, |
| }; |
| |
| /** Human-readable message for CRC errors |
| * |
| * It seems as though several drivers neglect to strip the Ethernet |
| * CRC, which will cause the FCoE footer to be misplaced and result |
| * (coincidentally) in an "invalid CRC" error from FCoE. |
| */ |
| struct errortab fcoe_errors[] __errortab = { |
| __einfo_errortab ( EINFO_EINVAL_CRC ), |
| }; |