mirror of https://github.com/ipxe/ipxe.git
				
				
				
			
		
			
				
	
	
		
			1194 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
			
		
		
	
	
			1194 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
/*
 | 
						|
 * 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 ),
 | 
						|
};
 |