v4.19.13 snapshot.
diff --git a/drivers/scsi/fcoe/Makefile b/drivers/scsi/fcoe/Makefile
new file mode 100644
index 0000000..aed0f5d
--- /dev/null
+++ b/drivers/scsi/fcoe/Makefile
@@ -0,0 +1,4 @@
+obj-$(CONFIG_FCOE) += fcoe.o
+obj-$(CONFIG_LIBFCOE) += libfcoe.o
+
+libfcoe-objs := fcoe_ctlr.o fcoe_transport.o fcoe_sysfs.o
diff --git a/drivers/scsi/fcoe/fcoe.c b/drivers/scsi/fcoe/fcoe.c
new file mode 100644
index 0000000..f46b312
--- /dev/null
+++ b/drivers/scsi/fcoe/fcoe.c
@@ -0,0 +1,2851 @@
+/*
+ * Copyright(c) 2007 - 2009 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Maintained at www.Open-FCoE.org
+ */
+
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
+#include <linux/crc32.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/fs.h>
+#include <linux/sysfs.h>
+#include <linux/ctype.h>
+#include <linux/workqueue.h>
+#include <net/dcbnl.h>
+#include <net/dcbevent.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/scsicam.h>
+#include <scsi/scsi_transport.h>
+#include <scsi/scsi_transport_fc.h>
+#include <net/rtnetlink.h>
+
+#include <scsi/fc/fc_encaps.h>
+#include <scsi/fc/fc_fip.h>
+#include <scsi/fc/fc_fcoe.h>
+
+#include <scsi/libfc.h>
+#include <scsi/fc_frame.h>
+#include <scsi/libfcoe.h>
+
+#include "fcoe.h"
+
+MODULE_AUTHOR("Open-FCoE.org");
+MODULE_DESCRIPTION("FCoE");
+MODULE_LICENSE("GPL v2");
+
+/* Performance tuning parameters for fcoe */
+static unsigned int fcoe_ddp_min = 4096;
+module_param_named(ddp_min, fcoe_ddp_min, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(ddp_min, "Minimum I/O size in bytes for "	\
+		 "Direct Data Placement (DDP).");
+
+unsigned int fcoe_debug_logging;
+module_param_named(debug_logging, fcoe_debug_logging, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels");
+
+static unsigned int fcoe_e_d_tov = 2 * 1000;
+module_param_named(e_d_tov, fcoe_e_d_tov, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(e_d_tov, "E_D_TOV in ms, default 2000");
+
+static unsigned int fcoe_r_a_tov = 2 * 2 * 1000;
+module_param_named(r_a_tov, fcoe_r_a_tov, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(r_a_tov, "R_A_TOV in ms, default 4000");
+
+static DEFINE_MUTEX(fcoe_config_mutex);
+
+static struct workqueue_struct *fcoe_wq;
+
+/* fcoe host list */
+/* must only by accessed under the RTNL mutex */
+static LIST_HEAD(fcoe_hostlist);
+static DEFINE_PER_CPU(struct fcoe_percpu_s, fcoe_percpu);
+
+/* Function Prototypes */
+static int fcoe_reset(struct Scsi_Host *);
+static int fcoe_xmit(struct fc_lport *, struct fc_frame *);
+static int fcoe_rcv(struct sk_buff *, struct net_device *,
+		    struct packet_type *, struct net_device *);
+static void fcoe_percpu_clean(struct fc_lport *);
+static int fcoe_link_ok(struct fc_lport *);
+
+static struct fc_lport *fcoe_hostlist_lookup(const struct net_device *);
+static int fcoe_hostlist_add(const struct fc_lport *);
+static void fcoe_hostlist_del(const struct fc_lport *);
+
+static int fcoe_device_notification(struct notifier_block *, ulong, void *);
+static void fcoe_dev_setup(void);
+static void fcoe_dev_cleanup(void);
+static struct fcoe_interface
+*fcoe_hostlist_lookup_port(const struct net_device *);
+
+static int fcoe_fip_recv(struct sk_buff *, struct net_device *,
+			 struct packet_type *, struct net_device *);
+static int fcoe_fip_vlan_recv(struct sk_buff *, struct net_device *,
+			      struct packet_type *, struct net_device *);
+
+static void fcoe_fip_send(struct fcoe_ctlr *, struct sk_buff *);
+static void fcoe_update_src_mac(struct fc_lport *, u8 *);
+static u8 *fcoe_get_src_mac(struct fc_lport *);
+static void fcoe_destroy_work(struct work_struct *);
+
+static int fcoe_ddp_setup(struct fc_lport *, u16, struct scatterlist *,
+			  unsigned int);
+static int fcoe_ddp_done(struct fc_lport *, u16);
+static int fcoe_ddp_target(struct fc_lport *, u16, struct scatterlist *,
+			   unsigned int);
+static int fcoe_dcb_app_notification(struct notifier_block *notifier,
+				     ulong event, void *ptr);
+
+static bool fcoe_match(struct net_device *netdev);
+static int fcoe_create(struct net_device *netdev, enum fip_mode fip_mode);
+static int fcoe_destroy(struct net_device *netdev);
+static int fcoe_enable(struct net_device *netdev);
+static int fcoe_disable(struct net_device *netdev);
+
+/* fcoe_syfs control interface handlers */
+static int fcoe_ctlr_alloc(struct net_device *netdev);
+static int fcoe_ctlr_enabled(struct fcoe_ctlr_device *cdev);
+static void fcoe_ctlr_mode(struct fcoe_ctlr_device *ctlr_dev);
+
+static struct fc_seq *fcoe_elsct_send(struct fc_lport *,
+				      u32 did, struct fc_frame *,
+				      unsigned int op,
+				      void (*resp)(struct fc_seq *,
+						   struct fc_frame *,
+						   void *),
+				      void *, u32 timeout);
+static void fcoe_recv_frame(struct sk_buff *skb);
+
+/* notification function for packets from net device */
+static struct notifier_block fcoe_notifier = {
+	.notifier_call = fcoe_device_notification,
+};
+
+/* notification function for DCB events */
+static struct notifier_block dcb_notifier = {
+	.notifier_call = fcoe_dcb_app_notification,
+};
+
+static struct scsi_transport_template *fcoe_nport_scsi_transport;
+static struct scsi_transport_template *fcoe_vport_scsi_transport;
+
+static int fcoe_vport_destroy(struct fc_vport *);
+static int fcoe_vport_create(struct fc_vport *, bool disabled);
+static int fcoe_vport_disable(struct fc_vport *, bool disable);
+static void fcoe_set_vport_symbolic_name(struct fc_vport *);
+static void fcoe_set_port_id(struct fc_lport *, u32, struct fc_frame *);
+static void fcoe_fcf_get_vlan_id(struct fcoe_fcf_device *);
+static void fcoe_vport_remove(struct fc_lport *);
+
+static struct fcoe_sysfs_function_template fcoe_sysfs_templ = {
+	.set_fcoe_ctlr_mode = fcoe_ctlr_mode,
+	.set_fcoe_ctlr_enabled = fcoe_ctlr_enabled,
+	.get_fcoe_ctlr_link_fail = fcoe_ctlr_get_lesb,
+	.get_fcoe_ctlr_vlink_fail = fcoe_ctlr_get_lesb,
+	.get_fcoe_ctlr_miss_fka = fcoe_ctlr_get_lesb,
+	.get_fcoe_ctlr_symb_err = fcoe_ctlr_get_lesb,
+	.get_fcoe_ctlr_err_block = fcoe_ctlr_get_lesb,
+	.get_fcoe_ctlr_fcs_error = fcoe_ctlr_get_lesb,
+
+	.get_fcoe_fcf_selected = fcoe_fcf_get_selected,
+	.get_fcoe_fcf_vlan_id = fcoe_fcf_get_vlan_id,
+};
+
+static struct libfc_function_template fcoe_libfc_fcn_templ = {
+	.frame_send = fcoe_xmit,
+	.ddp_setup = fcoe_ddp_setup,
+	.ddp_done = fcoe_ddp_done,
+	.ddp_target = fcoe_ddp_target,
+	.elsct_send = fcoe_elsct_send,
+	.get_lesb = fcoe_get_lesb,
+	.lport_set_port_id = fcoe_set_port_id,
+};
+
+static struct fc_function_template fcoe_nport_fc_functions = {
+	.show_host_node_name = 1,
+	.show_host_port_name = 1,
+	.show_host_supported_classes = 1,
+	.show_host_supported_fc4s = 1,
+	.show_host_active_fc4s = 1,
+	.show_host_maxframe_size = 1,
+	.show_host_serial_number = 1,
+	.show_host_manufacturer = 1,
+	.show_host_model = 1,
+	.show_host_model_description = 1,
+	.show_host_hardware_version = 1,
+	.show_host_driver_version = 1,
+	.show_host_firmware_version = 1,
+	.show_host_optionrom_version = 1,
+
+	.show_host_port_id = 1,
+	.show_host_supported_speeds = 1,
+	.get_host_speed = fc_get_host_speed,
+	.show_host_speed = 1,
+	.show_host_port_type = 1,
+	.get_host_port_state = fc_get_host_port_state,
+	.show_host_port_state = 1,
+	.show_host_symbolic_name = 1,
+
+	.dd_fcrport_size = sizeof(struct fc_rport_libfc_priv),
+	.show_rport_maxframe_size = 1,
+	.show_rport_supported_classes = 1,
+
+	.show_host_fabric_name = 1,
+	.show_starget_node_name = 1,
+	.show_starget_port_name = 1,
+	.show_starget_port_id = 1,
+	.set_rport_dev_loss_tmo = fc_set_rport_loss_tmo,
+	.show_rport_dev_loss_tmo = 1,
+	.get_fc_host_stats = fc_get_host_stats,
+	.issue_fc_host_lip = fcoe_reset,
+
+	.terminate_rport_io = fc_rport_terminate_io,
+
+	.vport_create = fcoe_vport_create,
+	.vport_delete = fcoe_vport_destroy,
+	.vport_disable = fcoe_vport_disable,
+	.set_vport_symbolic_name = fcoe_set_vport_symbolic_name,
+
+	.bsg_request = fc_lport_bsg_request,
+};
+
+static struct fc_function_template fcoe_vport_fc_functions = {
+	.show_host_node_name = 1,
+	.show_host_port_name = 1,
+	.show_host_supported_classes = 1,
+	.show_host_supported_fc4s = 1,
+	.show_host_active_fc4s = 1,
+	.show_host_maxframe_size = 1,
+	.show_host_serial_number = 1,
+	.show_host_manufacturer = 1,
+	.show_host_model = 1,
+	.show_host_model_description = 1,
+	.show_host_hardware_version = 1,
+	.show_host_driver_version = 1,
+	.show_host_firmware_version = 1,
+	.show_host_optionrom_version = 1,
+
+	.show_host_port_id = 1,
+	.show_host_supported_speeds = 1,
+	.get_host_speed = fc_get_host_speed,
+	.show_host_speed = 1,
+	.show_host_port_type = 1,
+	.get_host_port_state = fc_get_host_port_state,
+	.show_host_port_state = 1,
+	.show_host_symbolic_name = 1,
+
+	.dd_fcrport_size = sizeof(struct fc_rport_libfc_priv),
+	.show_rport_maxframe_size = 1,
+	.show_rport_supported_classes = 1,
+
+	.show_host_fabric_name = 1,
+	.show_starget_node_name = 1,
+	.show_starget_port_name = 1,
+	.show_starget_port_id = 1,
+	.set_rport_dev_loss_tmo = fc_set_rport_loss_tmo,
+	.show_rport_dev_loss_tmo = 1,
+	.get_fc_host_stats = fc_get_host_stats,
+	.issue_fc_host_lip = fcoe_reset,
+
+	.terminate_rport_io = fc_rport_terminate_io,
+
+	.bsg_request = fc_lport_bsg_request,
+};
+
+static struct scsi_host_template fcoe_shost_template = {
+	.module = THIS_MODULE,
+	.name = "FCoE Driver",
+	.proc_name = FCOE_NAME,
+	.queuecommand = fc_queuecommand,
+	.eh_timed_out = fc_eh_timed_out,
+	.eh_abort_handler = fc_eh_abort,
+	.eh_device_reset_handler = fc_eh_device_reset,
+	.eh_host_reset_handler = fc_eh_host_reset,
+	.slave_alloc = fc_slave_alloc,
+	.change_queue_depth = scsi_change_queue_depth,
+	.this_id = -1,
+	.cmd_per_lun = 3,
+	.can_queue = FCOE_MAX_OUTSTANDING_COMMANDS,
+	.use_clustering = ENABLE_CLUSTERING,
+	.sg_tablesize = SG_ALL,
+	.max_sectors = 0xffff,
+	.track_queue_depth = 1,
+};
+
+/**
+ * fcoe_interface_setup() - Setup a FCoE interface
+ * @fcoe:   The new FCoE interface
+ * @netdev: The net device that the fcoe interface is on
+ *
+ * Returns : 0 for success
+ * Locking: must be called with the RTNL mutex held
+ */
+static int fcoe_interface_setup(struct fcoe_interface *fcoe,
+				struct net_device *netdev)
+{
+	struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe);
+	struct netdev_hw_addr *ha;
+	struct net_device *real_dev;
+	u8 flogi_maddr[ETH_ALEN];
+	const struct net_device_ops *ops;
+
+	fcoe->netdev = netdev;
+
+	/* Let LLD initialize for FCoE */
+	ops = netdev->netdev_ops;
+	if (ops->ndo_fcoe_enable) {
+		if (ops->ndo_fcoe_enable(netdev))
+			FCOE_NETDEV_DBG(netdev, "Failed to enable FCoE"
+					" specific feature for LLD.\n");
+	}
+
+	/* Do not support for bonding device */
+	if (netdev->priv_flags & IFF_BONDING && netdev->flags & IFF_MASTER) {
+		FCOE_NETDEV_DBG(netdev, "Bonded interfaces not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	/* look for SAN MAC address, if multiple SAN MACs exist, only
+	 * use the first one for SPMA */
+	real_dev = is_vlan_dev(netdev) ? vlan_dev_real_dev(netdev) : netdev;
+	fcoe->realdev = real_dev;
+	rcu_read_lock();
+	for_each_dev_addr(real_dev, ha) {
+		if ((ha->type == NETDEV_HW_ADDR_T_SAN) &&
+		    (is_valid_ether_addr(ha->addr))) {
+			memcpy(fip->ctl_src_addr, ha->addr, ETH_ALEN);
+			fip->spma = 1;
+			break;
+		}
+	}
+	rcu_read_unlock();
+
+	/* setup Source Mac Address */
+	if (!fip->spma)
+		memcpy(fip->ctl_src_addr, netdev->dev_addr, netdev->addr_len);
+
+	/*
+	 * Add FCoE MAC address as second unicast MAC address
+	 * or enter promiscuous mode if not capable of listening
+	 * for multiple unicast MACs.
+	 */
+	memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN);
+	dev_uc_add(netdev, flogi_maddr);
+	if (fip->spma)
+		dev_uc_add(netdev, fip->ctl_src_addr);
+	if (fip->mode == FIP_MODE_VN2VN) {
+		dev_mc_add(netdev, FIP_ALL_VN2VN_MACS);
+		dev_mc_add(netdev, FIP_ALL_P2P_MACS);
+	} else
+		dev_mc_add(netdev, FIP_ALL_ENODE_MACS);
+
+	/*
+	 * setup the receive function from ethernet driver
+	 * on the ethertype for the given device
+	 */
+	fcoe->fcoe_packet_type.func = fcoe_rcv;
+	fcoe->fcoe_packet_type.type = htons(ETH_P_FCOE);
+	fcoe->fcoe_packet_type.dev = netdev;
+	dev_add_pack(&fcoe->fcoe_packet_type);
+
+	fcoe->fip_packet_type.func = fcoe_fip_recv;
+	fcoe->fip_packet_type.type = htons(ETH_P_FIP);
+	fcoe->fip_packet_type.dev = netdev;
+	dev_add_pack(&fcoe->fip_packet_type);
+
+	if (netdev != real_dev) {
+		fcoe->fip_vlan_packet_type.func = fcoe_fip_vlan_recv;
+		fcoe->fip_vlan_packet_type.type = htons(ETH_P_FIP);
+		fcoe->fip_vlan_packet_type.dev = real_dev;
+		dev_add_pack(&fcoe->fip_vlan_packet_type);
+	}
+	return 0;
+}
+
+/**
+ * fcoe_interface_create() - Create a FCoE interface on a net device
+ * @netdev: The net device to create the FCoE interface on
+ * @fip_mode: The mode to use for FIP
+ *
+ * Returns: pointer to a struct fcoe_interface or NULL on error
+ */
+static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev,
+						    enum fip_state fip_mode)
+{
+	struct fcoe_ctlr_device *ctlr_dev;
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_interface *fcoe;
+	int size;
+	int err;
+
+	if (!try_module_get(THIS_MODULE)) {
+		FCOE_NETDEV_DBG(netdev,
+				"Could not get a reference to the module\n");
+		fcoe = ERR_PTR(-EBUSY);
+		goto out;
+	}
+
+	size = sizeof(struct fcoe_ctlr) + sizeof(struct fcoe_interface);
+	ctlr_dev = fcoe_ctlr_device_add(&netdev->dev, &fcoe_sysfs_templ,
+					size);
+	if (!ctlr_dev) {
+		FCOE_DBG("Failed to add fcoe_ctlr_device\n");
+		fcoe = ERR_PTR(-ENOMEM);
+		goto out_putmod;
+	}
+
+	ctlr = fcoe_ctlr_device_priv(ctlr_dev);
+	ctlr->cdev = ctlr_dev;
+	fcoe = fcoe_ctlr_priv(ctlr);
+
+	dev_hold(netdev);
+
+	/*
+	 * Initialize FIP.
+	 */
+	fcoe_ctlr_init(ctlr, fip_mode);
+	ctlr->send = fcoe_fip_send;
+	ctlr->update_mac = fcoe_update_src_mac;
+	ctlr->get_src_addr = fcoe_get_src_mac;
+
+	err = fcoe_interface_setup(fcoe, netdev);
+	if (err) {
+		fcoe_ctlr_destroy(ctlr);
+		fcoe_ctlr_device_delete(ctlr_dev);
+		dev_put(netdev);
+		fcoe = ERR_PTR(err);
+		goto out_putmod;
+	}
+
+	goto out;
+
+out_putmod:
+	module_put(THIS_MODULE);
+out:
+	return fcoe;
+}
+
+/**
+ * fcoe_interface_remove() - remove FCoE interface from netdev
+ * @fcoe: The FCoE interface to be cleaned up
+ *
+ * Caller must be holding the RTNL mutex
+ */
+static void fcoe_interface_remove(struct fcoe_interface *fcoe)
+{
+	struct net_device *netdev = fcoe->netdev;
+	struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe);
+	u8 flogi_maddr[ETH_ALEN];
+	const struct net_device_ops *ops;
+
+	/*
+	 * Don't listen for Ethernet packets anymore.
+	 * synchronize_net() ensures that the packet handlers are not running
+	 * on another CPU. dev_remove_pack() would do that, this calls the
+	 * unsyncronized version __dev_remove_pack() to avoid multiple delays.
+	 */
+	__dev_remove_pack(&fcoe->fcoe_packet_type);
+	__dev_remove_pack(&fcoe->fip_packet_type);
+	if (netdev != fcoe->realdev)
+		__dev_remove_pack(&fcoe->fip_vlan_packet_type);
+	synchronize_net();
+
+	/* Delete secondary MAC addresses */
+	memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN);
+	dev_uc_del(netdev, flogi_maddr);
+	if (fip->spma)
+		dev_uc_del(netdev, fip->ctl_src_addr);
+	if (fip->mode == FIP_MODE_VN2VN) {
+		dev_mc_del(netdev, FIP_ALL_VN2VN_MACS);
+		dev_mc_del(netdev, FIP_ALL_P2P_MACS);
+	} else
+		dev_mc_del(netdev, FIP_ALL_ENODE_MACS);
+
+	/* Tell the LLD we are done w/ FCoE */
+	ops = netdev->netdev_ops;
+	if (ops->ndo_fcoe_disable) {
+		if (ops->ndo_fcoe_disable(netdev))
+			FCOE_NETDEV_DBG(netdev, "Failed to disable FCoE"
+					" specific feature for LLD.\n");
+	}
+	fcoe->removed = 1;
+}
+
+
+/**
+ * fcoe_interface_cleanup() - Clean up a FCoE interface
+ * @fcoe: The FCoE interface to be cleaned up
+ */
+static void fcoe_interface_cleanup(struct fcoe_interface *fcoe)
+{
+	struct net_device *netdev = fcoe->netdev;
+	struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe);
+
+	/* Release the self-reference taken during fcoe_interface_create() */
+	/* tear-down the FCoE controller */
+	fcoe_ctlr_destroy(fip);
+	scsi_host_put(fip->lp->host);
+	dev_put(netdev);
+	module_put(THIS_MODULE);
+}
+
+/**
+ * fcoe_fip_recv() - Handler for received FIP frames
+ * @skb:      The receive skb
+ * @netdev:   The associated net device
+ * @ptype:    The packet_type structure which was used to register this handler
+ * @orig_dev: The original net_device the skb was received on.
+ *	      (in case dev is a bond)
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_fip_recv(struct sk_buff *skb, struct net_device *netdev,
+			 struct packet_type *ptype,
+			 struct net_device *orig_dev)
+{
+	struct fcoe_interface *fcoe;
+	struct fcoe_ctlr *ctlr;
+
+	fcoe = container_of(ptype, struct fcoe_interface, fip_packet_type);
+	ctlr = fcoe_to_ctlr(fcoe);
+	fcoe_ctlr_recv(ctlr, skb);
+	return 0;
+}
+
+/**
+ * fcoe_fip_vlan_recv() - Handler for received FIP VLAN discovery frames
+ * @skb:      The receive skb
+ * @netdev:   The associated net device
+ * @ptype:    The packet_type structure which was used to register this handler
+ * @orig_dev: The original net_device the skb was received on.
+ *	      (in case dev is a bond)
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_fip_vlan_recv(struct sk_buff *skb, struct net_device *netdev,
+			      struct packet_type *ptype,
+			      struct net_device *orig_dev)
+{
+	struct fcoe_interface *fcoe;
+	struct fcoe_ctlr *ctlr;
+
+	fcoe = container_of(ptype, struct fcoe_interface, fip_vlan_packet_type);
+	ctlr = fcoe_to_ctlr(fcoe);
+	fcoe_ctlr_recv(ctlr, skb);
+	return 0;
+}
+
+/**
+ * fcoe_port_send() - Send an Ethernet-encapsulated FIP/FCoE frame
+ * @port: The FCoE port
+ * @skb: The FIP/FCoE packet to be sent
+ */
+static void fcoe_port_send(struct fcoe_port *port, struct sk_buff *skb)
+{
+	if (port->fcoe_pending_queue.qlen)
+		fcoe_check_wait_queue(port->lport, skb);
+	else if (fcoe_start_io(skb))
+		fcoe_check_wait_queue(port->lport, skb);
+}
+
+/**
+ * fcoe_fip_send() - Send an Ethernet-encapsulated FIP frame
+ * @fip: The FCoE controller
+ * @skb: The FIP packet to be sent
+ */
+static void fcoe_fip_send(struct fcoe_ctlr *fip, struct sk_buff *skb)
+{
+	struct fcoe_interface *fcoe = fcoe_from_ctlr(fip);
+	struct fip_frame {
+		struct ethhdr eth;
+		struct fip_header fip;
+	} __packed *frame;
+
+	/*
+	 * Use default VLAN for FIP VLAN discovery protocol
+	 */
+	frame = (struct fip_frame *)skb->data;
+	if (ntohs(frame->eth.h_proto) == ETH_P_FIP &&
+	    ntohs(frame->fip.fip_op) == FIP_OP_VLAN &&
+	    fcoe->realdev != fcoe->netdev)
+		skb->dev = fcoe->realdev;
+	else
+		skb->dev = fcoe->netdev;
+	fcoe_port_send(lport_priv(fip->lp), skb);
+}
+
+/**
+ * fcoe_update_src_mac() - Update the Ethernet MAC filters
+ * @lport: The local port to update the source MAC on
+ * @addr:  Unicast MAC address to add
+ *
+ * Remove any previously-set unicast MAC filter.
+ * Add secondary FCoE MAC address filter for our OUI.
+ */
+static void fcoe_update_src_mac(struct fc_lport *lport, u8 *addr)
+{
+	struct fcoe_port *port = lport_priv(lport);
+	struct fcoe_interface *fcoe = port->priv;
+
+	if (!is_zero_ether_addr(port->data_src_addr))
+		dev_uc_del(fcoe->netdev, port->data_src_addr);
+	if (!is_zero_ether_addr(addr))
+		dev_uc_add(fcoe->netdev, addr);
+	memcpy(port->data_src_addr, addr, ETH_ALEN);
+}
+
+/**
+ * fcoe_get_src_mac() - return the Ethernet source address for an lport
+ * @lport: libfc lport
+ */
+static u8 *fcoe_get_src_mac(struct fc_lport *lport)
+{
+	struct fcoe_port *port = lport_priv(lport);
+
+	return port->data_src_addr;
+}
+
+/**
+ * fcoe_lport_config() - Set up a local port
+ * @lport: The local port to be setup
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_lport_config(struct fc_lport *lport)
+{
+	lport->link_up = 0;
+	lport->qfull = 0;
+	lport->max_retry_count = 3;
+	lport->max_rport_retry_count = 3;
+	lport->e_d_tov = fcoe_e_d_tov;
+	lport->r_a_tov = fcoe_r_a_tov;
+	lport->service_params = (FCP_SPPF_INIT_FCN | FCP_SPPF_RD_XRDY_DIS |
+				 FCP_SPPF_RETRY | FCP_SPPF_CONF_COMPL);
+	lport->does_npiv = 1;
+
+	fc_lport_init_stats(lport);
+
+	/* lport fc_lport related configuration */
+	fc_lport_config(lport);
+
+	/* offload related configuration */
+	lport->crc_offload = 0;
+	lport->seq_offload = 0;
+	lport->lro_enabled = 0;
+	lport->lro_xid = 0;
+	lport->lso_max = 0;
+
+	return 0;
+}
+
+/**
+ * fcoe_netdev_features_change - Updates the lport's offload flags based
+ * on the LLD netdev's FCoE feature flags
+ */
+static void fcoe_netdev_features_change(struct fc_lport *lport,
+					struct net_device *netdev)
+{
+	mutex_lock(&lport->lp_mutex);
+
+	if (netdev->features & NETIF_F_SG)
+		lport->sg_supp = 1;
+	else
+		lport->sg_supp = 0;
+
+	if (netdev->features & NETIF_F_FCOE_CRC) {
+		lport->crc_offload = 1;
+		FCOE_NETDEV_DBG(netdev, "Supports FCCRC offload\n");
+	} else {
+		lport->crc_offload = 0;
+	}
+
+	if (netdev->features & NETIF_F_FSO) {
+		lport->seq_offload = 1;
+		lport->lso_max = netdev->gso_max_size;
+		FCOE_NETDEV_DBG(netdev, "Supports LSO for max len 0x%x\n",
+				lport->lso_max);
+	} else {
+		lport->seq_offload = 0;
+		lport->lso_max = 0;
+	}
+
+	if (netdev->fcoe_ddp_xid) {
+		lport->lro_enabled = 1;
+		lport->lro_xid = netdev->fcoe_ddp_xid;
+		FCOE_NETDEV_DBG(netdev, "Supports LRO for max xid 0x%x\n",
+				lport->lro_xid);
+	} else {
+		lport->lro_enabled = 0;
+		lport->lro_xid = 0;
+	}
+
+	mutex_unlock(&lport->lp_mutex);
+}
+
+/**
+ * fcoe_netdev_config() - Set up net devive for SW FCoE
+ * @lport:  The local port that is associated with the net device
+ * @netdev: The associated net device
+ *
+ * Must be called after fcoe_lport_config() as it will use local port mutex
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_netdev_config(struct fc_lport *lport, struct net_device *netdev)
+{
+	u32 mfs;
+	u64 wwnn, wwpn;
+	struct fcoe_interface *fcoe;
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_port *port;
+
+	/* Setup lport private data to point to fcoe softc */
+	port = lport_priv(lport);
+	fcoe = port->priv;
+	ctlr = fcoe_to_ctlr(fcoe);
+
+	/* Figure out the VLAN ID, if any */
+	if (is_vlan_dev(netdev))
+		lport->vlan = vlan_dev_vlan_id(netdev);
+	else
+		lport->vlan = 0;
+
+	/*
+	 * Determine max frame size based on underlying device and optional
+	 * user-configured limit.  If the MFS is too low, fcoe_link_ok()
+	 * will return 0, so do this first.
+	 */
+	mfs = netdev->mtu;
+	if (netdev->features & NETIF_F_FCOE_MTU) {
+		mfs = FCOE_MTU;
+		FCOE_NETDEV_DBG(netdev, "Supports FCOE_MTU of %d bytes\n", mfs);
+	}
+	mfs -= (sizeof(struct fcoe_hdr) + sizeof(struct fcoe_crc_eof));
+	if (fc_set_mfs(lport, mfs))
+		return -EINVAL;
+
+	/* offload features support */
+	fcoe_netdev_features_change(lport, netdev);
+
+	skb_queue_head_init(&port->fcoe_pending_queue);
+	port->fcoe_pending_queue_active = 0;
+	timer_setup(&port->timer, fcoe_queue_timer, 0);
+
+	fcoe_link_speed_update(lport);
+
+	if (!lport->vport) {
+		if (fcoe_get_wwn(netdev, &wwnn, NETDEV_FCOE_WWNN))
+			wwnn = fcoe_wwn_from_mac(ctlr->ctl_src_addr, 1, 0);
+		fc_set_wwnn(lport, wwnn);
+		if (fcoe_get_wwn(netdev, &wwpn, NETDEV_FCOE_WWPN))
+			wwpn = fcoe_wwn_from_mac(ctlr->ctl_src_addr,
+						 2, 0);
+		fc_set_wwpn(lport, wwpn);
+	}
+
+	return 0;
+}
+
+/**
+ * fcoe_shost_config() - Set up the SCSI host associated with a local port
+ * @lport: The local port
+ * @dev:   The device associated with the SCSI host
+ *
+ * Must be called after fcoe_lport_config() and fcoe_netdev_config()
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_shost_config(struct fc_lport *lport, struct device *dev)
+{
+	int rc = 0;
+
+	/* lport scsi host config */
+	lport->host->max_lun = FCOE_MAX_LUN;
+	lport->host->max_id = FCOE_MAX_FCP_TARGET;
+	lport->host->max_channel = 0;
+	lport->host->max_cmd_len = FCOE_MAX_CMD_LEN;
+
+	if (lport->vport)
+		lport->host->transportt = fcoe_vport_scsi_transport;
+	else
+		lport->host->transportt = fcoe_nport_scsi_transport;
+
+	/* add the new host to the SCSI-ml */
+	rc = scsi_add_host(lport->host, dev);
+	if (rc) {
+		FCOE_NETDEV_DBG(fcoe_netdev(lport), "fcoe_shost_config: "
+				"error on scsi_add_host\n");
+		return rc;
+	}
+
+	if (!lport->vport)
+		fc_host_max_npiv_vports(lport->host) = USHRT_MAX;
+
+	snprintf(fc_host_symbolic_name(lport->host), FC_SYMBOLIC_NAME_SIZE,
+		 "%s v%s over %s", FCOE_NAME, FCOE_VERSION,
+		 fcoe_netdev(lport)->name);
+
+	return 0;
+}
+
+
+/**
+ * fcoe_fdmi_info() - Get FDMI related info from net devive for SW FCoE
+ * @lport:  The local port that is associated with the net device
+ * @netdev: The associated net device
+ *
+ * Must be called after fcoe_shost_config() as it will use local port mutex
+ *
+ */
+static void fcoe_fdmi_info(struct fc_lport *lport, struct net_device *netdev)
+{
+	struct fcoe_interface *fcoe;
+	struct fcoe_port *port;
+	struct net_device *realdev;
+	int rc;
+
+	port = lport_priv(lport);
+	fcoe = port->priv;
+	realdev = fcoe->realdev;
+
+	/* No FDMI state m/c for NPIV ports */
+	if (lport->vport)
+		return;
+
+	if (realdev->netdev_ops->ndo_fcoe_get_hbainfo) {
+		struct netdev_fcoe_hbainfo *fdmi;
+		fdmi = kzalloc(sizeof(*fdmi), GFP_KERNEL);
+		if (!fdmi)
+			return;
+
+		rc = realdev->netdev_ops->ndo_fcoe_get_hbainfo(realdev,
+							       fdmi);
+		if (rc) {
+			printk(KERN_INFO "fcoe: Failed to retrieve FDMI "
+					"information from netdev.\n");
+			return;
+		}
+
+		snprintf(fc_host_serial_number(lport->host),
+			 FC_SERIAL_NUMBER_SIZE,
+			 "%s",
+			 fdmi->serial_number);
+		snprintf(fc_host_manufacturer(lport->host),
+			 FC_SERIAL_NUMBER_SIZE,
+			 "%s",
+			 fdmi->manufacturer);
+		snprintf(fc_host_model(lport->host),
+			 FC_SYMBOLIC_NAME_SIZE,
+			 "%s",
+			 fdmi->model);
+		snprintf(fc_host_model_description(lport->host),
+			 FC_SYMBOLIC_NAME_SIZE,
+			 "%s",
+			 fdmi->model_description);
+		snprintf(fc_host_hardware_version(lport->host),
+			 FC_VERSION_STRING_SIZE,
+			 "%s",
+			 fdmi->hardware_version);
+		snprintf(fc_host_driver_version(lport->host),
+			 FC_VERSION_STRING_SIZE,
+			 "%s",
+			 fdmi->driver_version);
+		snprintf(fc_host_optionrom_version(lport->host),
+			 FC_VERSION_STRING_SIZE,
+			 "%s",
+			 fdmi->optionrom_version);
+		snprintf(fc_host_firmware_version(lport->host),
+			 FC_VERSION_STRING_SIZE,
+			 "%s",
+			 fdmi->firmware_version);
+
+		/* Enable FDMI lport states */
+		lport->fdmi_enabled = 1;
+		kfree(fdmi);
+	} else {
+		lport->fdmi_enabled = 0;
+		printk(KERN_INFO "fcoe: No FDMI support.\n");
+	}
+}
+
+/**
+ * fcoe_oem_match() - The match routine for the offloaded exchange manager
+ * @fp: The I/O frame
+ *
+ * This routine will be associated with an exchange manager (EM). When
+ * the libfc exchange handling code is looking for an EM to use it will
+ * call this routine and pass it the frame that it wishes to send. This
+ * routine will return True if the associated EM is to be used and False
+ * if the echange code should continue looking for an EM.
+ *
+ * The offload EM that this routine is associated with will handle any
+ * packets that are for SCSI read requests.
+ *
+ * This has been enhanced to work when FCoE stack is operating in target
+ * mode.
+ *
+ * Returns: True for read types I/O, otherwise returns false.
+ */
+static bool fcoe_oem_match(struct fc_frame *fp)
+{
+	struct fc_frame_header *fh = fc_frame_header_get(fp);
+	struct fcp_cmnd *fcp;
+
+	if (fc_fcp_is_read(fr_fsp(fp)) &&
+	    (fr_fsp(fp)->data_len > fcoe_ddp_min))
+		return true;
+	else if ((fr_fsp(fp) == NULL) &&
+		 (fh->fh_r_ctl == FC_RCTL_DD_UNSOL_CMD) &&
+		 (ntohs(fh->fh_rx_id) == FC_XID_UNKNOWN)) {
+		fcp = fc_frame_payload_get(fp, sizeof(*fcp));
+		if ((fcp->fc_flags & FCP_CFL_WRDATA) &&
+		    (ntohl(fcp->fc_dl) > fcoe_ddp_min))
+			return true;
+	}
+	return false;
+}
+
+/**
+ * fcoe_em_config() - Allocate and configure an exchange manager
+ * @lport: The local port that the new EM will be associated with
+ *
+ * Returns: 0 on success
+ */
+static inline int fcoe_em_config(struct fc_lport *lport)
+{
+	struct fcoe_port *port = lport_priv(lport);
+	struct fcoe_interface *fcoe = port->priv;
+	struct fcoe_interface *oldfcoe = NULL;
+	struct net_device *old_real_dev, *cur_real_dev;
+	u16 min_xid = FCOE_MIN_XID;
+	u16 max_xid = FCOE_MAX_XID;
+
+	/*
+	 * Check if need to allocate an em instance for
+	 * offload exchange ids to be shared across all VN_PORTs/lport.
+	 */
+	if (!lport->lro_enabled || !lport->lro_xid ||
+	    (lport->lro_xid >= max_xid)) {
+		lport->lro_xid = 0;
+		goto skip_oem;
+	}
+
+	/*
+	 * Reuse existing offload em instance in case
+	 * it is already allocated on real eth device
+	 */
+	if (is_vlan_dev(fcoe->netdev))
+		cur_real_dev = vlan_dev_real_dev(fcoe->netdev);
+	else
+		cur_real_dev = fcoe->netdev;
+
+	list_for_each_entry(oldfcoe, &fcoe_hostlist, list) {
+		if (is_vlan_dev(oldfcoe->netdev))
+			old_real_dev = vlan_dev_real_dev(oldfcoe->netdev);
+		else
+			old_real_dev = oldfcoe->netdev;
+
+		if (cur_real_dev == old_real_dev) {
+			fcoe->oem = oldfcoe->oem;
+			break;
+		}
+	}
+
+	if (fcoe->oem) {
+		if (!fc_exch_mgr_add(lport, fcoe->oem, fcoe_oem_match)) {
+			printk(KERN_ERR "fcoe_em_config: failed to add "
+			       "offload em:%p on interface:%s\n",
+			       fcoe->oem, fcoe->netdev->name);
+			return -ENOMEM;
+		}
+	} else {
+		fcoe->oem = fc_exch_mgr_alloc(lport, FC_CLASS_3,
+					      FCOE_MIN_XID, lport->lro_xid,
+					      fcoe_oem_match);
+		if (!fcoe->oem) {
+			printk(KERN_ERR "fcoe_em_config: failed to allocate "
+			       "em for offload exches on interface:%s\n",
+			       fcoe->netdev->name);
+			return -ENOMEM;
+		}
+	}
+
+	/*
+	 * Exclude offload EM xid range from next EM xid range.
+	 */
+	min_xid += lport->lro_xid + 1;
+
+skip_oem:
+	if (!fc_exch_mgr_alloc(lport, FC_CLASS_3, min_xid, max_xid, NULL)) {
+		printk(KERN_ERR "fcoe_em_config: failed to "
+		       "allocate em on interface %s\n", fcoe->netdev->name);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/**
+ * fcoe_if_destroy() - Tear down a SW FCoE instance
+ * @lport: The local port to be destroyed
+ *
+ * Locking: Must be called with the RTNL mutex held.
+ *
+ */
+static void fcoe_if_destroy(struct fc_lport *lport)
+{
+	struct fcoe_port *port = lport_priv(lport);
+	struct fcoe_interface *fcoe = port->priv;
+	struct net_device *netdev = fcoe->netdev;
+
+	FCOE_NETDEV_DBG(netdev, "Destroying interface\n");
+
+	/* Logout of the fabric */
+	fc_fabric_logoff(lport);
+
+	/* Cleanup the fc_lport */
+	fc_lport_destroy(lport);
+
+	/* Stop the transmit retry timer */
+	del_timer_sync(&port->timer);
+
+	/* Free existing transmit skbs */
+	fcoe_clean_pending_queue(lport);
+
+	if (!is_zero_ether_addr(port->data_src_addr))
+		dev_uc_del(netdev, port->data_src_addr);
+	if (lport->vport)
+		synchronize_net();
+	else
+		fcoe_interface_remove(fcoe);
+
+	/* Free queued packets for the per-CPU receive threads */
+	fcoe_percpu_clean(lport);
+
+	/* Detach from the scsi-ml */
+	fc_remove_host(lport->host);
+	scsi_remove_host(lport->host);
+
+	/* Destroy lport scsi_priv */
+	fc_fcp_destroy(lport);
+
+	/* There are no more rports or I/O, free the EM */
+	fc_exch_mgr_free(lport);
+
+	/* Free memory used by statistical counters */
+	fc_lport_free_stats(lport);
+
+	/*
+	 * Release the Scsi_Host for vport but hold on to
+	 * master lport until it fcoe interface fully cleaned-up.
+	 */
+	if (lport->vport)
+		scsi_host_put(lport->host);
+}
+
+/**
+ * fcoe_ddp_setup() - Call a LLD's ddp_setup through the net device
+ * @lport: The local port to setup DDP for
+ * @xid:   The exchange ID for this DDP transfer
+ * @sgl:   The scatterlist describing this transfer
+ * @sgc:   The number of sg items
+ *
+ * Returns: 0 if the DDP context was not configured
+ */
+static int fcoe_ddp_setup(struct fc_lport *lport, u16 xid,
+			  struct scatterlist *sgl, unsigned int sgc)
+{
+	struct net_device *netdev = fcoe_netdev(lport);
+
+	if (netdev->netdev_ops->ndo_fcoe_ddp_setup)
+		return netdev->netdev_ops->ndo_fcoe_ddp_setup(netdev,
+							      xid, sgl,
+							      sgc);
+
+	return 0;
+}
+
+/**
+ * fcoe_ddp_target() - Call a LLD's ddp_target through the net device
+ * @lport: The local port to setup DDP for
+ * @xid:   The exchange ID for this DDP transfer
+ * @sgl:   The scatterlist describing this transfer
+ * @sgc:   The number of sg items
+ *
+ * Returns: 0 if the DDP context was not configured
+ */
+static int fcoe_ddp_target(struct fc_lport *lport, u16 xid,
+			   struct scatterlist *sgl, unsigned int sgc)
+{
+	struct net_device *netdev = fcoe_netdev(lport);
+
+	if (netdev->netdev_ops->ndo_fcoe_ddp_target)
+		return netdev->netdev_ops->ndo_fcoe_ddp_target(netdev, xid,
+							       sgl, sgc);
+
+	return 0;
+}
+
+
+/**
+ * fcoe_ddp_done() - Call a LLD's ddp_done through the net device
+ * @lport: The local port to complete DDP on
+ * @xid:   The exchange ID for this DDP transfer
+ *
+ * Returns: the length of data that have been completed by DDP
+ */
+static int fcoe_ddp_done(struct fc_lport *lport, u16 xid)
+{
+	struct net_device *netdev = fcoe_netdev(lport);
+
+	if (netdev->netdev_ops->ndo_fcoe_ddp_done)
+		return netdev->netdev_ops->ndo_fcoe_ddp_done(netdev, xid);
+	return 0;
+}
+
+/**
+ * fcoe_if_create() - Create a FCoE instance on an interface
+ * @fcoe:   The FCoE interface to create a local port on
+ * @parent: The device pointer to be the parent in sysfs for the SCSI host
+ * @npiv:   Indicates if the port is a vport or not
+ *
+ * Creates a fc_lport instance and a Scsi_Host instance and configure them.
+ *
+ * Returns: The allocated fc_lport or an error pointer
+ */
+static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe,
+				       struct device *parent, int npiv)
+{
+	struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe);
+	struct net_device *netdev = fcoe->netdev;
+	struct fc_lport *lport, *n_port;
+	struct fcoe_port *port;
+	struct Scsi_Host *shost;
+	int rc;
+	/*
+	 * parent is only a vport if npiv is 1,
+	 * but we'll only use vport in that case so go ahead and set it
+	 */
+	struct fc_vport *vport = dev_to_vport(parent);
+
+	FCOE_NETDEV_DBG(netdev, "Create Interface\n");
+
+	if (!npiv)
+		lport = libfc_host_alloc(&fcoe_shost_template, sizeof(*port));
+	else
+		lport = libfc_vport_create(vport, sizeof(*port));
+
+	if (!lport) {
+		FCOE_NETDEV_DBG(netdev, "Could not allocate host structure\n");
+		rc = -ENOMEM;
+		goto out;
+	}
+	port = lport_priv(lport);
+	port->lport = lport;
+	port->priv = fcoe;
+	port->get_netdev = fcoe_netdev;
+	port->max_queue_depth = FCOE_MAX_QUEUE_DEPTH;
+	port->min_queue_depth = FCOE_MIN_QUEUE_DEPTH;
+	INIT_WORK(&port->destroy_work, fcoe_destroy_work);
+
+	/*
+	 * Need to add the lport to the hostlist
+	 * so we catch NETDEV_CHANGE events.
+	 */
+	fcoe_hostlist_add(lport);
+
+	/* configure a fc_lport including the exchange manager */
+	rc = fcoe_lport_config(lport);
+	if (rc) {
+		FCOE_NETDEV_DBG(netdev, "Could not configure lport for the "
+				"interface\n");
+		goto out_host_put;
+	}
+
+	if (npiv) {
+		FCOE_NETDEV_DBG(netdev, "Setting vport names, "
+				"%16.16llx %16.16llx\n",
+				vport->node_name, vport->port_name);
+		fc_set_wwnn(lport, vport->node_name);
+		fc_set_wwpn(lport, vport->port_name);
+	}
+
+	/* configure lport network properties */
+	rc = fcoe_netdev_config(lport, netdev);
+	if (rc) {
+		FCOE_NETDEV_DBG(netdev, "Could not configure netdev for the "
+				"interface\n");
+		goto out_lp_destroy;
+	}
+
+	/* configure lport scsi host properties */
+	rc = fcoe_shost_config(lport, parent);
+	if (rc) {
+		FCOE_NETDEV_DBG(netdev, "Could not configure shost for the "
+				"interface\n");
+		goto out_lp_destroy;
+	}
+
+	/* Initialize the library */
+	rc = fcoe_libfc_config(lport, ctlr, &fcoe_libfc_fcn_templ, 1);
+	if (rc) {
+		FCOE_NETDEV_DBG(netdev, "Could not configure libfc for the "
+				"interface\n");
+		goto out_lp_destroy;
+	}
+
+	/* Initialized FDMI information */
+	fcoe_fdmi_info(lport, netdev);
+
+	/*
+	 * fcoe_em_alloc() and fcoe_hostlist_add() both
+	 * need to be atomic with respect to other changes to the
+	 * hostlist since fcoe_em_alloc() looks for an existing EM
+	 * instance on host list updated by fcoe_hostlist_add().
+	 *
+	 * This is currently handled through the fcoe_config_mutex
+	 * begin held.
+	 */
+	if (!npiv)
+		/* lport exch manager allocation */
+		rc = fcoe_em_config(lport);
+	else {
+		shost = vport_to_shost(vport);
+		n_port = shost_priv(shost);
+		rc = fc_exch_mgr_list_clone(n_port, lport);
+	}
+
+	if (rc) {
+		FCOE_NETDEV_DBG(netdev, "Could not configure the EM\n");
+		goto out_lp_destroy;
+	}
+
+	return lport;
+
+out_lp_destroy:
+	fc_exch_mgr_free(lport);
+out_host_put:
+	fcoe_hostlist_del(lport);
+	scsi_host_put(lport->host);
+out:
+	return ERR_PTR(rc);
+}
+
+/**
+ * fcoe_if_init() - Initialization routine for fcoe.ko
+ *
+ * Attaches the SW FCoE transport to the FC transport
+ *
+ * Returns: 0 on success
+ */
+static int __init fcoe_if_init(void)
+{
+	/* attach to scsi transport */
+	fcoe_nport_scsi_transport =
+		fc_attach_transport(&fcoe_nport_fc_functions);
+	fcoe_vport_scsi_transport =
+		fc_attach_transport(&fcoe_vport_fc_functions);
+
+	if (!fcoe_nport_scsi_transport) {
+		printk(KERN_ERR "fcoe: Failed to attach to the FC transport\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+/**
+ * fcoe_if_exit() - Tear down fcoe.ko
+ *
+ * Detaches the SW FCoE transport from the FC transport
+ *
+ * Returns: 0 on success
+ */
+static int __exit fcoe_if_exit(void)
+{
+	fc_release_transport(fcoe_nport_scsi_transport);
+	fc_release_transport(fcoe_vport_scsi_transport);
+	fcoe_nport_scsi_transport = NULL;
+	fcoe_vport_scsi_transport = NULL;
+	return 0;
+}
+
+static void fcoe_thread_cleanup_local(unsigned int cpu)
+{
+	struct page *crc_eof;
+	struct fcoe_percpu_s *p;
+
+	p = per_cpu_ptr(&fcoe_percpu, cpu);
+	spin_lock_bh(&p->fcoe_rx_list.lock);
+	crc_eof = p->crc_eof_page;
+	p->crc_eof_page = NULL;
+	p->crc_eof_offset = 0;
+	spin_unlock_bh(&p->fcoe_rx_list.lock);
+
+	if (crc_eof)
+		put_page(crc_eof);
+	flush_work(&p->work);
+}
+
+/**
+ * fcoe_select_cpu() - Selects CPU to handle post-processing of incoming
+ *			command.
+ *
+ * This routine selects next CPU based on cpumask to distribute
+ * incoming requests in round robin.
+ *
+ * Returns: int CPU number
+ */
+static inline unsigned int fcoe_select_cpu(void)
+{
+	static unsigned int selected_cpu;
+
+	selected_cpu = cpumask_next(selected_cpu, cpu_online_mask);
+	if (selected_cpu >= nr_cpu_ids)
+		selected_cpu = cpumask_first(cpu_online_mask);
+
+	return selected_cpu;
+}
+
+/**
+ * fcoe_rcv() - Receive packets from a net device
+ * @skb:    The received packet
+ * @netdev: The net device that the packet was received on
+ * @ptype:  The packet type context
+ * @olddev: The last device net device
+ *
+ * This routine is called by NET_RX_SOFTIRQ. It receives a packet, builds a
+ * FC frame and passes the frame to libfc.
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_rcv(struct sk_buff *skb, struct net_device *netdev,
+	     struct packet_type *ptype, struct net_device *olddev)
+{
+	struct fc_lport *lport;
+	struct fcoe_rcv_info *fr;
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_interface *fcoe;
+	struct fc_frame_header *fh;
+	struct fcoe_percpu_s *fps;
+	struct ethhdr *eh;
+	unsigned int cpu;
+
+	fcoe = container_of(ptype, struct fcoe_interface, fcoe_packet_type);
+	ctlr = fcoe_to_ctlr(fcoe);
+	lport = ctlr->lp;
+	if (unlikely(!lport)) {
+		FCOE_NETDEV_DBG(netdev, "Cannot find hba structure\n");
+		goto err2;
+	}
+	if (!lport->link_up)
+		goto err2;
+
+	FCOE_NETDEV_DBG(netdev,
+			"skb_info: len:%d data_len:%d head:%p data:%p tail:%p end:%p sum:%d dev:%s\n",
+			skb->len, skb->data_len, skb->head, skb->data,
+			skb_tail_pointer(skb), skb_end_pointer(skb),
+			skb->csum, skb->dev ? skb->dev->name : "<NULL>");
+
+
+	skb = skb_share_check(skb, GFP_ATOMIC);
+
+	if (skb == NULL)
+		return NET_RX_DROP;
+
+	eh = eth_hdr(skb);
+
+	if (is_fip_mode(ctlr) &&
+	    !ether_addr_equal(eh->h_source, ctlr->dest_addr)) {
+		FCOE_NETDEV_DBG(netdev, "wrong source mac address:%pM\n",
+				eh->h_source);
+		goto err;
+	}
+
+	/*
+	 * Check for minimum frame length, and make sure required FCoE
+	 * and FC headers are pulled into the linear data area.
+	 */
+	if (unlikely((skb->len < FCOE_MIN_FRAME) ||
+		     !pskb_may_pull(skb, FCOE_HEADER_LEN)))
+		goto err;
+
+	skb_set_transport_header(skb, sizeof(struct fcoe_hdr));
+	fh = (struct fc_frame_header *) skb_transport_header(skb);
+
+	if (ntoh24(&eh->h_dest[3]) != ntoh24(fh->fh_d_id)) {
+		FCOE_NETDEV_DBG(netdev, "FC frame d_id mismatch with MAC:%pM\n",
+				eh->h_dest);
+		goto err;
+	}
+
+	fr = fcoe_dev_from_skb(skb);
+	fr->fr_dev = lport;
+
+	/*
+	 * In case the incoming frame's exchange is originated from
+	 * the initiator, then received frame's exchange id is ANDed
+	 * with fc_cpu_mask bits to get the same cpu on which exchange
+	 * was originated, otherwise select cpu using rx exchange id
+	 * or fcoe_select_cpu().
+	 */
+	if (ntoh24(fh->fh_f_ctl) & FC_FC_EX_CTX)
+		cpu = ntohs(fh->fh_ox_id) & fc_cpu_mask;
+	else {
+		if (ntohs(fh->fh_rx_id) == FC_XID_UNKNOWN)
+			cpu = fcoe_select_cpu();
+		else
+			cpu = ntohs(fh->fh_rx_id) & fc_cpu_mask;
+	}
+
+	if (cpu >= nr_cpu_ids)
+		goto err;
+
+	fps = &per_cpu(fcoe_percpu, cpu);
+	spin_lock(&fps->fcoe_rx_list.lock);
+	/*
+	 * We now have a valid CPU that we're targeting for
+	 * this skb. We also have this receive thread locked,
+	 * so we're free to queue skbs into it's queue.
+	 */
+
+	/*
+	 * Note: We used to have a set of conditions under which we would
+	 * call fcoe_recv_frame directly, rather than queuing to the rx list
+	 * as it could save a few cycles, but doing so is prohibited, as
+	 * fcoe_recv_frame has several paths that may sleep, which is forbidden
+	 * in softirq context.
+	 */
+	__skb_queue_tail(&fps->fcoe_rx_list, skb);
+	schedule_work_on(cpu, &fps->work);
+	spin_unlock(&fps->fcoe_rx_list.lock);
+
+	return NET_RX_SUCCESS;
+err:
+	per_cpu_ptr(lport->stats, get_cpu())->ErrorFrames++;
+	put_cpu();
+err2:
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+/**
+ * fcoe_alloc_paged_crc_eof() - Allocate a page to be used for the trailer CRC
+ * @skb:  The packet to be transmitted
+ * @tlen: The total length of the trailer
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_alloc_paged_crc_eof(struct sk_buff *skb, int tlen)
+{
+	struct fcoe_percpu_s *fps;
+	int rc;
+
+	fps = &get_cpu_var(fcoe_percpu);
+	rc = fcoe_get_paged_crc_eof(skb, tlen, fps);
+	put_cpu_var(fcoe_percpu);
+
+	return rc;
+}
+
+/**
+ * fcoe_xmit() - Transmit a FCoE frame
+ * @lport: The local port that the frame is to be transmitted for
+ * @fp:	   The frame to be transmitted
+ *
+ * Return: 0 for success
+ */
+static int fcoe_xmit(struct fc_lport *lport, struct fc_frame *fp)
+{
+	int wlen;
+	u32 crc;
+	struct ethhdr *eh;
+	struct fcoe_crc_eof *cp;
+	struct sk_buff *skb;
+	struct fc_stats *stats;
+	struct fc_frame_header *fh;
+	unsigned int hlen;		/* header length implies the version */
+	unsigned int tlen;		/* trailer length */
+	unsigned int elen;		/* eth header, may include vlan */
+	struct fcoe_port *port = lport_priv(lport);
+	struct fcoe_interface *fcoe = port->priv;
+	struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe);
+	u8 sof, eof;
+	struct fcoe_hdr *hp;
+
+	WARN_ON((fr_len(fp) % sizeof(u32)) != 0);
+
+	fh = fc_frame_header_get(fp);
+	skb = fp_skb(fp);
+	wlen = skb->len / FCOE_WORD_TO_BYTE;
+
+	if (!lport->link_up) {
+		kfree_skb(skb);
+		return 0;
+	}
+
+	if (unlikely(fh->fh_type == FC_TYPE_ELS) &&
+	    fcoe_ctlr_els_send(ctlr, lport, skb))
+		return 0;
+
+	sof = fr_sof(fp);
+	eof = fr_eof(fp);
+
+	elen = sizeof(struct ethhdr);
+	hlen = sizeof(struct fcoe_hdr);
+	tlen = sizeof(struct fcoe_crc_eof);
+	wlen = (skb->len - tlen + sizeof(crc)) / FCOE_WORD_TO_BYTE;
+
+	/* crc offload */
+	if (likely(lport->crc_offload)) {
+		skb->ip_summed = CHECKSUM_PARTIAL;
+		skb->csum_start = skb_headroom(skb);
+		skb->csum_offset = skb->len;
+		crc = 0;
+	} else {
+		skb->ip_summed = CHECKSUM_NONE;
+		crc = fcoe_fc_crc(fp);
+	}
+
+	/* copy port crc and eof to the skb buff */
+	if (skb_is_nonlinear(skb)) {
+		skb_frag_t *frag;
+		if (fcoe_alloc_paged_crc_eof(skb, tlen)) {
+			kfree_skb(skb);
+			return -ENOMEM;
+		}
+		frag = &skb_shinfo(skb)->frags[skb_shinfo(skb)->nr_frags - 1];
+		cp = kmap_atomic(skb_frag_page(frag))
+			+ frag->page_offset;
+	} else {
+		cp = skb_put(skb, tlen);
+	}
+
+	memset(cp, 0, sizeof(*cp));
+	cp->fcoe_eof = eof;
+	cp->fcoe_crc32 = cpu_to_le32(~crc);
+
+	if (skb_is_nonlinear(skb)) {
+		kunmap_atomic(cp);
+		cp = NULL;
+	}
+
+	/* adjust skb network/transport offsets to match mac/fcoe/port */
+	skb_push(skb, elen + hlen);
+	skb_reset_mac_header(skb);
+	skb_reset_network_header(skb);
+	skb->mac_len = elen;
+	skb->protocol = htons(ETH_P_FCOE);
+	skb->priority = fcoe->priority;
+
+	if (is_vlan_dev(fcoe->netdev) &&
+	    fcoe->realdev->features & NETIF_F_HW_VLAN_CTAG_TX) {
+		/* must set skb->dev before calling vlan_put_tag */
+		skb->dev = fcoe->realdev;
+		__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
+				       vlan_dev_vlan_id(fcoe->netdev));
+	} else
+		skb->dev = fcoe->netdev;
+
+	/* fill up mac and fcoe headers */
+	eh = eth_hdr(skb);
+	eh->h_proto = htons(ETH_P_FCOE);
+	memcpy(eh->h_dest, ctlr->dest_addr, ETH_ALEN);
+	if (ctlr->map_dest)
+		memcpy(eh->h_dest + 3, fh->fh_d_id, 3);
+
+	if (unlikely(ctlr->flogi_oxid != FC_XID_UNKNOWN))
+		memcpy(eh->h_source, ctlr->ctl_src_addr, ETH_ALEN);
+	else
+		memcpy(eh->h_source, port->data_src_addr, ETH_ALEN);
+
+	hp = (struct fcoe_hdr *)(eh + 1);
+	memset(hp, 0, sizeof(*hp));
+	if (FC_FCOE_VER)
+		FC_FCOE_ENCAPS_VER(hp, FC_FCOE_VER);
+	hp->fcoe_sof = sof;
+
+	/* fcoe lso, mss is in max_payload which is non-zero for FCP data */
+	if (lport->seq_offload && fr_max_payload(fp)) {
+		skb_shinfo(skb)->gso_type = SKB_GSO_FCOE;
+		skb_shinfo(skb)->gso_size = fr_max_payload(fp);
+	} else {
+		skb_shinfo(skb)->gso_type = 0;
+		skb_shinfo(skb)->gso_size = 0;
+	}
+	/* update tx stats: regardless if LLD fails */
+	stats = per_cpu_ptr(lport->stats, get_cpu());
+	stats->TxFrames++;
+	stats->TxWords += wlen;
+	put_cpu();
+
+	/* send down to lld */
+	fr_dev(fp) = lport;
+	fcoe_port_send(port, skb);
+	return 0;
+}
+
+/**
+ * fcoe_filter_frames() - filter out bad fcoe frames, i.e. bad CRC
+ * @lport: The local port the frame was received on
+ * @fp:	   The received frame
+ *
+ * Return: 0 on passing filtering checks
+ */
+static inline int fcoe_filter_frames(struct fc_lport *lport,
+				     struct fc_frame *fp)
+{
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_interface *fcoe;
+	struct fc_frame_header *fh;
+	struct sk_buff *skb = (struct sk_buff *)fp;
+	struct fc_stats *stats;
+
+	/*
+	 * We only check CRC if no offload is available and if it is
+	 * it's solicited data, in which case, the FCP layer would
+	 * check it during the copy.
+	 */
+	if (lport->crc_offload && skb->ip_summed == CHECKSUM_UNNECESSARY)
+		fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED;
+	else
+		fr_flags(fp) |= FCPHF_CRC_UNCHECKED;
+
+	fh = (struct fc_frame_header *) skb_transport_header(skb);
+	fh = fc_frame_header_get(fp);
+	if (fh->fh_r_ctl == FC_RCTL_DD_SOL_DATA && fh->fh_type == FC_TYPE_FCP)
+		return 0;
+
+	fcoe = ((struct fcoe_port *)lport_priv(lport))->priv;
+	ctlr = fcoe_to_ctlr(fcoe);
+	if (is_fip_mode(ctlr) && fc_frame_payload_op(fp) == ELS_LOGO &&
+	    ntoh24(fh->fh_s_id) == FC_FID_FLOGI) {
+		FCOE_DBG("fcoe: dropping FCoE lport LOGO in fip mode\n");
+		return -EINVAL;
+	}
+
+	if (!(fr_flags(fp) & FCPHF_CRC_UNCHECKED) ||
+	    le32_to_cpu(fr_crc(fp)) == ~crc32(~0, skb->data, skb->len)) {
+		fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED;
+		return 0;
+	}
+
+	stats = per_cpu_ptr(lport->stats, get_cpu());
+	stats->InvalidCRCCount++;
+	if (stats->InvalidCRCCount < 5)
+		printk(KERN_WARNING "fcoe: dropping frame with CRC error\n");
+	put_cpu();
+	return -EINVAL;
+}
+
+/**
+ * fcoe_recv_frame() - process a single received frame
+ * @skb: frame to process
+ */
+static void fcoe_recv_frame(struct sk_buff *skb)
+{
+	u32 fr_len;
+	struct fc_lport *lport;
+	struct fcoe_rcv_info *fr;
+	struct fc_stats *stats;
+	struct fcoe_crc_eof crc_eof;
+	struct fc_frame *fp;
+	struct fcoe_port *port;
+	struct fcoe_hdr *hp;
+
+	fr = fcoe_dev_from_skb(skb);
+	lport = fr->fr_dev;
+	if (unlikely(!lport)) {
+		FCOE_NETDEV_DBG(skb->dev, "NULL lport in skb\n");
+		kfree_skb(skb);
+		return;
+	}
+
+	FCOE_NETDEV_DBG(skb->dev,
+			"skb_info: len:%d data_len:%d head:%p data:%p tail:%p end:%p sum:%d dev:%s\n",
+			skb->len, skb->data_len,
+			skb->head, skb->data, skb_tail_pointer(skb),
+			skb_end_pointer(skb), skb->csum,
+			skb->dev ? skb->dev->name : "<NULL>");
+
+	port = lport_priv(lport);
+	skb_linearize(skb); /* check for skb_is_nonlinear is within skb_linearize */
+
+	/*
+	 * Frame length checks and setting up the header pointers
+	 * was done in fcoe_rcv already.
+	 */
+	hp = (struct fcoe_hdr *) skb_network_header(skb);
+
+	stats = per_cpu_ptr(lport->stats, get_cpu());
+	if (unlikely(FC_FCOE_DECAPS_VER(hp) != FC_FCOE_VER)) {
+		if (stats->ErrorFrames < 5)
+			printk(KERN_WARNING "fcoe: FCoE version "
+			       "mismatch: The frame has "
+			       "version %x, but the "
+			       "initiator supports version "
+			       "%x\n", FC_FCOE_DECAPS_VER(hp),
+			       FC_FCOE_VER);
+		goto drop;
+	}
+
+	skb_pull(skb, sizeof(struct fcoe_hdr));
+	fr_len = skb->len - sizeof(struct fcoe_crc_eof);
+
+	stats->RxFrames++;
+	stats->RxWords += fr_len / FCOE_WORD_TO_BYTE;
+
+	fp = (struct fc_frame *)skb;
+	fc_frame_init(fp);
+	fr_dev(fp) = lport;
+	fr_sof(fp) = hp->fcoe_sof;
+
+	/* Copy out the CRC and EOF trailer for access */
+	if (skb_copy_bits(skb, fr_len, &crc_eof, sizeof(crc_eof)))
+		goto drop;
+	fr_eof(fp) = crc_eof.fcoe_eof;
+	fr_crc(fp) = crc_eof.fcoe_crc32;
+	if (pskb_trim(skb, fr_len))
+		goto drop;
+
+	if (!fcoe_filter_frames(lport, fp)) {
+		put_cpu();
+		fc_exch_recv(lport, fp);
+		return;
+	}
+drop:
+	stats->ErrorFrames++;
+	put_cpu();
+	kfree_skb(skb);
+}
+
+/**
+ * fcoe_receive_work() - The per-CPU worker
+ * @work: The work struct
+ *
+ */
+static void fcoe_receive_work(struct work_struct *work)
+{
+	struct fcoe_percpu_s *p;
+	struct sk_buff *skb;
+	struct sk_buff_head tmp;
+
+	p = container_of(work, struct fcoe_percpu_s, work);
+	skb_queue_head_init(&tmp);
+
+	spin_lock_bh(&p->fcoe_rx_list.lock);
+	skb_queue_splice_init(&p->fcoe_rx_list, &tmp);
+	spin_unlock_bh(&p->fcoe_rx_list.lock);
+
+	if (!skb_queue_len(&tmp))
+		return;
+
+	while ((skb = __skb_dequeue(&tmp)))
+		fcoe_recv_frame(skb);
+}
+
+/**
+ * fcoe_dev_setup() - Setup the link change notification interface
+ */
+static void fcoe_dev_setup(void)
+{
+	register_dcbevent_notifier(&dcb_notifier);
+	register_netdevice_notifier(&fcoe_notifier);
+}
+
+/**
+ * fcoe_dev_cleanup() - Cleanup the link change notification interface
+ */
+static void fcoe_dev_cleanup(void)
+{
+	unregister_dcbevent_notifier(&dcb_notifier);
+	unregister_netdevice_notifier(&fcoe_notifier);
+}
+
+static struct fcoe_interface *
+fcoe_hostlist_lookup_realdev_port(struct net_device *netdev)
+{
+	struct fcoe_interface *fcoe;
+	struct net_device *real_dev;
+
+	list_for_each_entry(fcoe, &fcoe_hostlist, list) {
+		if (is_vlan_dev(fcoe->netdev))
+			real_dev = vlan_dev_real_dev(fcoe->netdev);
+		else
+			real_dev = fcoe->netdev;
+
+		if (netdev == real_dev)
+			return fcoe;
+	}
+	return NULL;
+}
+
+static int fcoe_dcb_app_notification(struct notifier_block *notifier,
+				     ulong event, void *ptr)
+{
+	struct dcb_app_type *entry = ptr;
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_interface *fcoe;
+	struct net_device *netdev;
+	int prio;
+
+	if (entry->app.selector != DCB_APP_IDTYPE_ETHTYPE)
+		return NOTIFY_OK;
+
+	netdev = dev_get_by_index(&init_net, entry->ifindex);
+	if (!netdev)
+		return NOTIFY_OK;
+
+	fcoe = fcoe_hostlist_lookup_realdev_port(netdev);
+	dev_put(netdev);
+	if (!fcoe)
+		return NOTIFY_OK;
+
+	ctlr = fcoe_to_ctlr(fcoe);
+
+	if (entry->dcbx & DCB_CAP_DCBX_VER_CEE)
+		prio = ffs(entry->app.priority) - 1;
+	else
+		prio = entry->app.priority;
+
+	if (prio < 0)
+		return NOTIFY_OK;
+
+	if (entry->app.protocol == ETH_P_FIP ||
+	    entry->app.protocol == ETH_P_FCOE)
+		ctlr->priority = prio;
+
+	if (entry->app.protocol == ETH_P_FCOE)
+		fcoe->priority = prio;
+
+	return NOTIFY_OK;
+}
+
+/**
+ * fcoe_device_notification() - Handler for net device events
+ * @notifier: The context of the notification
+ * @event:    The type of event
+ * @ptr:      The net device that the event was on
+ *
+ * This function is called by the Ethernet driver in case of link change event.
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_device_notification(struct notifier_block *notifier,
+				    ulong event, void *ptr)
+{
+	struct fcoe_ctlr_device *cdev;
+	struct fc_lport *lport = NULL;
+	struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_interface *fcoe;
+	struct fcoe_port *port;
+	struct fc_stats *stats;
+	u32 link_possible = 1;
+	u32 mfs;
+	int rc = NOTIFY_OK;
+
+	list_for_each_entry(fcoe, &fcoe_hostlist, list) {
+		if (fcoe->netdev == netdev) {
+			ctlr = fcoe_to_ctlr(fcoe);
+			lport = ctlr->lp;
+			break;
+		}
+	}
+	if (!lport) {
+		rc = NOTIFY_DONE;
+		goto out;
+	}
+
+	switch (event) {
+	case NETDEV_DOWN:
+	case NETDEV_GOING_DOWN:
+		link_possible = 0;
+		break;
+	case NETDEV_UP:
+	case NETDEV_CHANGE:
+		break;
+	case NETDEV_CHANGEMTU:
+		if (netdev->features & NETIF_F_FCOE_MTU)
+			break;
+		mfs = netdev->mtu - (sizeof(struct fcoe_hdr) +
+				     sizeof(struct fcoe_crc_eof));
+		if (mfs >= FC_MIN_MAX_FRAME)
+			fc_set_mfs(lport, mfs);
+		break;
+	case NETDEV_REGISTER:
+		break;
+	case NETDEV_UNREGISTER:
+		list_del(&fcoe->list);
+		port = lport_priv(ctlr->lp);
+		fcoe_vport_remove(lport);
+		mutex_lock(&fcoe_config_mutex);
+		fcoe_if_destroy(lport);
+		if (!fcoe->removed)
+			fcoe_interface_remove(fcoe);
+		fcoe_interface_cleanup(fcoe);
+		mutex_unlock(&fcoe_config_mutex);
+		fcoe_ctlr_device_delete(fcoe_ctlr_to_ctlr_dev(ctlr));
+		goto out;
+		break;
+	case NETDEV_FEAT_CHANGE:
+		fcoe_netdev_features_change(lport, netdev);
+		break;
+	default:
+		FCOE_NETDEV_DBG(netdev, "Unknown event %ld "
+				"from netdev netlink\n", event);
+	}
+
+	fcoe_link_speed_update(lport);
+
+	cdev = fcoe_ctlr_to_ctlr_dev(ctlr);
+
+	if (link_possible && !fcoe_link_ok(lport)) {
+		switch (cdev->enabled) {
+		case FCOE_CTLR_DISABLED:
+			pr_info("Link up while interface is disabled.\n");
+			break;
+		case FCOE_CTLR_ENABLED:
+		case FCOE_CTLR_UNUSED:
+			fcoe_ctlr_link_up(ctlr);
+		};
+	} else if (fcoe_ctlr_link_down(ctlr)) {
+		switch (cdev->enabled) {
+		case FCOE_CTLR_DISABLED:
+			pr_info("Link down while interface is disabled.\n");
+			break;
+		case FCOE_CTLR_ENABLED:
+		case FCOE_CTLR_UNUSED:
+			stats = per_cpu_ptr(lport->stats, get_cpu());
+			stats->LinkFailureCount++;
+			put_cpu();
+			fcoe_clean_pending_queue(lport);
+		};
+	}
+out:
+	return rc;
+}
+
+/**
+ * fcoe_disable() - Disables a FCoE interface
+ * @netdev  : The net_device object the Ethernet interface to create on
+ *
+ * Called from fcoe transport.
+ *
+ * Returns: 0 for success
+ *
+ * Deprecated: use fcoe_ctlr_enabled()
+ */
+static int fcoe_disable(struct net_device *netdev)
+{
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_interface *fcoe;
+	int rc = 0;
+
+	mutex_lock(&fcoe_config_mutex);
+
+	rtnl_lock();
+	fcoe = fcoe_hostlist_lookup_port(netdev);
+	rtnl_unlock();
+
+	if (fcoe) {
+		ctlr = fcoe_to_ctlr(fcoe);
+		fcoe_ctlr_link_down(ctlr);
+		fcoe_clean_pending_queue(ctlr->lp);
+	} else
+		rc = -ENODEV;
+
+	mutex_unlock(&fcoe_config_mutex);
+	return rc;
+}
+
+/**
+ * fcoe_enable() - Enables a FCoE interface
+ * @netdev  : The net_device object the Ethernet interface to create on
+ *
+ * Called from fcoe transport.
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_enable(struct net_device *netdev)
+{
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_interface *fcoe;
+	int rc = 0;
+
+	mutex_lock(&fcoe_config_mutex);
+	rtnl_lock();
+	fcoe = fcoe_hostlist_lookup_port(netdev);
+	rtnl_unlock();
+
+	if (!fcoe) {
+		rc = -ENODEV;
+		goto out;
+	}
+
+	ctlr = fcoe_to_ctlr(fcoe);
+
+	if (!fcoe_link_ok(ctlr->lp))
+		fcoe_ctlr_link_up(ctlr);
+
+out:
+	mutex_unlock(&fcoe_config_mutex);
+	return rc;
+}
+
+/**
+ * fcoe_ctlr_enabled() - Enable or disable an FCoE Controller
+ * @cdev: The FCoE Controller that is being enabled or disabled
+ *
+ * fcoe_sysfs will ensure that the state of 'enabled' has
+ * changed, so no checking is necessary here. This routine simply
+ * calls fcoe_enable or fcoe_disable, both of which are deprecated.
+ * When those routines are removed the functionality can be merged
+ * here.
+ */
+static int fcoe_ctlr_enabled(struct fcoe_ctlr_device *cdev)
+{
+	struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(cdev);
+	struct fc_lport *lport = ctlr->lp;
+	struct net_device *netdev = fcoe_netdev(lport);
+
+	switch (cdev->enabled) {
+	case FCOE_CTLR_ENABLED:
+		return fcoe_enable(netdev);
+	case FCOE_CTLR_DISABLED:
+		return fcoe_disable(netdev);
+	case FCOE_CTLR_UNUSED:
+	default:
+		return -ENOTSUPP;
+	};
+}
+
+/**
+ * fcoe_ctlr_mode() - Switch FIP mode
+ * @cdev: The FCoE Controller that is being modified
+ *
+ * When the FIP mode has been changed we need to update
+ * the multicast addresses to ensure we get the correct
+ * frames.
+ */
+static void fcoe_ctlr_mode(struct fcoe_ctlr_device *ctlr_dev)
+{
+	struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev);
+	struct fcoe_interface *fcoe = fcoe_ctlr_priv(ctlr);
+
+	if (ctlr_dev->mode == FIP_CONN_TYPE_VN2VN &&
+	    ctlr->mode != FIP_MODE_VN2VN) {
+		dev_mc_del(fcoe->netdev, FIP_ALL_ENODE_MACS);
+		dev_mc_add(fcoe->netdev, FIP_ALL_VN2VN_MACS);
+		dev_mc_add(fcoe->netdev, FIP_ALL_P2P_MACS);
+	} else if (ctlr->mode != FIP_MODE_FABRIC) {
+		dev_mc_del(fcoe->netdev, FIP_ALL_VN2VN_MACS);
+		dev_mc_del(fcoe->netdev, FIP_ALL_P2P_MACS);
+		dev_mc_add(fcoe->netdev, FIP_ALL_ENODE_MACS);
+	}
+	fcoe_ctlr_set_fip_mode(ctlr_dev);
+}
+
+/**
+ * fcoe_destroy() - Destroy a FCoE interface
+ * @netdev  : The net_device object the Ethernet interface to create on
+ *
+ * Called from fcoe transport
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_destroy(struct net_device *netdev)
+{
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_interface *fcoe;
+	struct fc_lport *lport;
+	struct fcoe_port *port;
+	int rc = 0;
+
+	mutex_lock(&fcoe_config_mutex);
+	rtnl_lock();
+	fcoe = fcoe_hostlist_lookup_port(netdev);
+	if (!fcoe) {
+		rc = -ENODEV;
+		goto out_nodev;
+	}
+	ctlr = fcoe_to_ctlr(fcoe);
+	lport = ctlr->lp;
+	port = lport_priv(lport);
+	list_del(&fcoe->list);
+	queue_work(fcoe_wq, &port->destroy_work);
+out_nodev:
+	rtnl_unlock();
+	mutex_unlock(&fcoe_config_mutex);
+	return rc;
+}
+
+/**
+ * fcoe_destroy_work() - Destroy a FCoE port in a deferred work context
+ * @work: Handle to the FCoE port to be destroyed
+ */
+static void fcoe_destroy_work(struct work_struct *work)
+{
+	struct fcoe_ctlr_device *cdev;
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_port *port;
+	struct fcoe_interface *fcoe;
+
+	port = container_of(work, struct fcoe_port, destroy_work);
+
+	fcoe_vport_remove(port->lport);
+
+	mutex_lock(&fcoe_config_mutex);
+
+	fcoe = port->priv;
+	ctlr = fcoe_to_ctlr(fcoe);
+	cdev = fcoe_ctlr_to_ctlr_dev(ctlr);
+
+	rtnl_lock();
+	fcoe_if_destroy(port->lport);
+	if (!fcoe->removed)
+		fcoe_interface_remove(fcoe);
+	rtnl_unlock();
+	fcoe_interface_cleanup(fcoe);
+
+	mutex_unlock(&fcoe_config_mutex);
+
+	fcoe_ctlr_device_delete(cdev);
+}
+
+/**
+ * fcoe_match() - Check if the FCoE is supported on the given netdevice
+ * @netdev  : The net_device object the Ethernet interface to create on
+ *
+ * Called from fcoe transport.
+ *
+ * Returns: always returns true as this is the default FCoE transport,
+ * i.e., support all netdevs.
+ */
+static bool fcoe_match(struct net_device *netdev)
+{
+	return true;
+}
+
+/**
+ * fcoe_dcb_create() - Initialize DCB attributes and hooks
+ * @netdev: The net_device object of the L2 link that should be queried
+ * @port: The fcoe_port to bind FCoE APP priority with
+ * @
+ */
+static void fcoe_dcb_create(struct fcoe_interface *fcoe)
+{
+	int ctlr_prio = TC_PRIO_BESTEFFORT;
+	int fcoe_prio = TC_PRIO_INTERACTIVE;
+	struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe);
+#ifdef CONFIG_DCB
+	int dcbx;
+	u8 fup, up;
+	struct net_device *netdev = fcoe->realdev;
+	struct dcb_app app = {
+				.priority = 0,
+				.protocol = ETH_P_FCOE
+			     };
+
+	/* setup DCB priority attributes. */
+	if (netdev && netdev->dcbnl_ops && netdev->dcbnl_ops->getdcbx) {
+		dcbx = netdev->dcbnl_ops->getdcbx(netdev);
+
+		if (dcbx & DCB_CAP_DCBX_VER_IEEE) {
+			app.selector = IEEE_8021QAZ_APP_SEL_ETHERTYPE;
+			up = dcb_ieee_getapp_mask(netdev, &app);
+			app.protocol = ETH_P_FIP;
+			fup = dcb_ieee_getapp_mask(netdev, &app);
+		} else {
+			app.selector = DCB_APP_IDTYPE_ETHTYPE;
+			up = dcb_getapp(netdev, &app);
+			app.protocol = ETH_P_FIP;
+			fup = dcb_getapp(netdev, &app);
+		}
+
+		fcoe_prio = ffs(up) ? ffs(up) - 1 : 0;
+		ctlr_prio = ffs(fup) ? ffs(fup) - 1 : fcoe_prio;
+	}
+#endif
+	fcoe->priority = fcoe_prio;
+	ctlr->priority = ctlr_prio;
+}
+
+enum fcoe_create_link_state {
+	FCOE_CREATE_LINK_DOWN,
+	FCOE_CREATE_LINK_UP,
+};
+
+/**
+ * _fcoe_create() - (internal) Create a fcoe interface
+ * @netdev  :   The net_device object the Ethernet interface to create on
+ * @fip_mode:   The FIP mode for this creation
+ * @link_state: The ctlr link state on creation
+ *
+ * Called from either the libfcoe 'create' module parameter
+ * via fcoe_create or from fcoe_syfs's ctlr_create file.
+ *
+ * libfcoe's 'create' module parameter is deprecated so some
+ * consolidation of code can be done when that interface is
+ * removed.
+ */
+static int _fcoe_create(struct net_device *netdev, enum fip_mode fip_mode,
+			enum fcoe_create_link_state link_state)
+{
+	int rc = 0;
+	struct fcoe_ctlr_device *ctlr_dev;
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_interface *fcoe;
+	struct fc_lport *lport;
+
+	mutex_lock(&fcoe_config_mutex);
+	rtnl_lock();
+
+	/* look for existing lport */
+	if (fcoe_hostlist_lookup(netdev)) {
+		rc = -EEXIST;
+		goto out_nodev;
+	}
+
+	fcoe = fcoe_interface_create(netdev, fip_mode);
+	if (IS_ERR(fcoe)) {
+		rc = PTR_ERR(fcoe);
+		goto out_nodev;
+	}
+
+	ctlr = fcoe_to_ctlr(fcoe);
+	ctlr_dev = fcoe_ctlr_to_ctlr_dev(ctlr);
+	lport = fcoe_if_create(fcoe, &ctlr_dev->dev, 0);
+	if (IS_ERR(lport)) {
+		printk(KERN_ERR "fcoe: Failed to create interface (%s)\n",
+		       netdev->name);
+		rc = -EIO;
+		if (!fcoe->removed)
+			fcoe_interface_remove(fcoe);
+		rtnl_unlock();
+		fcoe_interface_cleanup(fcoe);
+		mutex_unlock(&fcoe_config_mutex);
+		fcoe_ctlr_device_delete(ctlr_dev);
+		return rc;
+	}
+
+	/* Make this the "master" N_Port */
+	ctlr->lp = lport;
+
+	/* setup DCB priority attributes. */
+	fcoe_dcb_create(fcoe);
+
+	/* start FIP Discovery and FLOGI */
+	lport->boot_time = jiffies;
+	fc_fabric_login(lport);
+
+	/*
+	 * If the fcoe_ctlr_device is to be set to DISABLED
+	 * it must be done after the lport is added to the
+	 * hostlist, but before the rtnl_lock is released.
+	 * This is because the rtnl_lock protects the
+	 * hostlist that fcoe_device_notification uses. If
+	 * the FCoE Controller is intended to be created
+	 * DISABLED then 'enabled' needs to be considered
+	 * handling link events. 'enabled' must be set
+	 * before the lport can be found in the hostlist
+	 * when a link up event is received.
+	 */
+	if (link_state == FCOE_CREATE_LINK_UP)
+		ctlr_dev->enabled = FCOE_CTLR_ENABLED;
+	else
+		ctlr_dev->enabled = FCOE_CTLR_DISABLED;
+
+	if (link_state == FCOE_CREATE_LINK_UP &&
+	    !fcoe_link_ok(lport)) {
+		rtnl_unlock();
+		fcoe_ctlr_link_up(ctlr);
+		mutex_unlock(&fcoe_config_mutex);
+		return rc;
+	}
+
+out_nodev:
+	rtnl_unlock();
+	mutex_unlock(&fcoe_config_mutex);
+
+	return rc;
+}
+
+/**
+ * fcoe_create() - Create a fcoe interface
+ * @netdev  : The net_device object the Ethernet interface to create on
+ * @fip_mode: The FIP mode for this creation
+ *
+ * Called from fcoe transport
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_create(struct net_device *netdev, enum fip_mode fip_mode)
+{
+	return _fcoe_create(netdev, fip_mode, FCOE_CREATE_LINK_UP);
+}
+
+/**
+ * fcoe_ctlr_alloc() - Allocate a fcoe interface from fcoe_sysfs
+ * @netdev: The net_device to be used by the allocated FCoE Controller
+ *
+ * This routine is called from fcoe_sysfs. It will start the fcoe_ctlr
+ * in a link_down state. The allows the user an opportunity to configure
+ * the FCoE Controller from sysfs before enabling the FCoE Controller.
+ *
+ * Creating in with this routine starts the FCoE Controller in Fabric
+ * mode. The user can change to VN2VN or another mode before enabling.
+ */
+static int fcoe_ctlr_alloc(struct net_device *netdev)
+{
+	return _fcoe_create(netdev, FIP_MODE_FABRIC,
+			    FCOE_CREATE_LINK_DOWN);
+}
+
+/**
+ * fcoe_link_ok() - Check if the link is OK for a local port
+ * @lport: The local port to check link on
+ *
+ * Returns: 0 if link is UP and OK, -1 if not
+ *
+ */
+static int fcoe_link_ok(struct fc_lport *lport)
+{
+	struct net_device *netdev = fcoe_netdev(lport);
+
+	if (netif_oper_up(netdev))
+		return 0;
+	return -1;
+}
+
+/**
+ * fcoe_percpu_clean() - Clear all pending skbs for an local port
+ * @lport: The local port whose skbs are to be cleared
+ *
+ * Must be called with fcoe_create_mutex held to single-thread completion.
+ *
+ * This flushes the pending skbs by flush the work item for each CPU. The work
+ * item on each possible CPU is flushed because we may have used the per-CPU
+ * struct of an offline CPU.
+ */
+static void fcoe_percpu_clean(struct fc_lport *lport)
+{
+	struct fcoe_percpu_s *pp;
+	unsigned int cpu;
+
+	for_each_possible_cpu(cpu) {
+		pp = &per_cpu(fcoe_percpu, cpu);
+
+		flush_work(&pp->work);
+	}
+}
+
+/**
+ * fcoe_reset() - Reset a local port
+ * @shost: The SCSI host associated with the local port to be reset
+ *
+ * Returns: Always 0 (return value required by FC transport template)
+ */
+static int fcoe_reset(struct Scsi_Host *shost)
+{
+	struct fc_lport *lport = shost_priv(shost);
+	struct fcoe_port *port = lport_priv(lport);
+	struct fcoe_interface *fcoe = port->priv;
+	struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe);
+	struct fcoe_ctlr_device *cdev = fcoe_ctlr_to_ctlr_dev(ctlr);
+
+	fcoe_ctlr_link_down(ctlr);
+	fcoe_clean_pending_queue(ctlr->lp);
+
+	if (cdev->enabled != FCOE_CTLR_DISABLED &&
+	    !fcoe_link_ok(ctlr->lp))
+		fcoe_ctlr_link_up(ctlr);
+	return 0;
+}
+
+/**
+ * fcoe_hostlist_lookup_port() - Find the FCoE interface associated with a net device
+ * @netdev: The net device used as a key
+ *
+ * Locking: Must be called with the RNL mutex held.
+ *
+ * Returns: NULL or the FCoE interface
+ */
+static struct fcoe_interface *
+fcoe_hostlist_lookup_port(const struct net_device *netdev)
+{
+	struct fcoe_interface *fcoe;
+
+	list_for_each_entry(fcoe, &fcoe_hostlist, list) {
+		if (fcoe->netdev == netdev)
+			return fcoe;
+	}
+	return NULL;
+}
+
+/**
+ * fcoe_hostlist_lookup() - Find the local port associated with a
+ *			    given net device
+ * @netdev: The netdevice used as a key
+ *
+ * Locking: Must be called with the RTNL mutex held
+ *
+ * Returns: NULL or the local port
+ */
+static struct fc_lport *fcoe_hostlist_lookup(const struct net_device *netdev)
+{
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_interface *fcoe;
+
+	fcoe = fcoe_hostlist_lookup_port(netdev);
+	ctlr = fcoe_to_ctlr(fcoe);
+	return (fcoe) ? ctlr->lp : NULL;
+}
+
+/**
+ * fcoe_hostlist_add() - Add the FCoE interface identified by a local
+ *			 port to the hostlist
+ * @lport: The local port that identifies the FCoE interface to be added
+ *
+ * Locking: must be called with the RTNL mutex held
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_hostlist_add(const struct fc_lport *lport)
+{
+	struct fcoe_interface *fcoe;
+	struct fcoe_port *port;
+
+	fcoe = fcoe_hostlist_lookup_port(fcoe_netdev(lport));
+	if (!fcoe) {
+		port = lport_priv(lport);
+		fcoe = port->priv;
+		list_add_tail(&fcoe->list, &fcoe_hostlist);
+	}
+	return 0;
+}
+
+/**
+ * fcoe_hostlist_del() - Remove the FCoE interface identified by a local
+ *			 port to the hostlist
+ * @lport: The local port that identifies the FCoE interface to be added
+ *
+ * Locking: must be called with the RTNL mutex held
+ *
+ */
+static void fcoe_hostlist_del(const struct fc_lport *lport)
+{
+	struct fcoe_interface *fcoe;
+	struct fcoe_port *port;
+
+	port = lport_priv(lport);
+	fcoe = port->priv;
+	list_del(&fcoe->list);
+	return;
+}
+
+static struct fcoe_transport fcoe_sw_transport = {
+	.name = {FCOE_TRANSPORT_DEFAULT},
+	.attached = false,
+	.list = LIST_HEAD_INIT(fcoe_sw_transport.list),
+	.match = fcoe_match,
+	.alloc = fcoe_ctlr_alloc,
+	.create = fcoe_create,
+	.destroy = fcoe_destroy,
+	.enable = fcoe_enable,
+	.disable = fcoe_disable,
+};
+
+/**
+ * fcoe_init() - Initialize fcoe.ko
+ *
+ * Returns: 0 on success, or a negative value on failure
+ */
+static int __init fcoe_init(void)
+{
+	struct fcoe_percpu_s *p;
+	unsigned int cpu;
+	int rc = 0;
+
+	fcoe_wq = alloc_workqueue("fcoe", 0, 0);
+	if (!fcoe_wq)
+		return -ENOMEM;
+
+	/* register as a fcoe transport */
+	rc = fcoe_transport_attach(&fcoe_sw_transport);
+	if (rc) {
+		printk(KERN_ERR "failed to register an fcoe transport, check "
+			"if libfcoe is loaded\n");
+		goto out_destroy;
+	}
+
+	mutex_lock(&fcoe_config_mutex);
+
+	for_each_possible_cpu(cpu) {
+		p = per_cpu_ptr(&fcoe_percpu, cpu);
+		INIT_WORK(&p->work, fcoe_receive_work);
+		skb_queue_head_init(&p->fcoe_rx_list);
+	}
+
+	/* Setup link change notification */
+	fcoe_dev_setup();
+
+	rc = fcoe_if_init();
+	if (rc)
+		goto out_free;
+
+	mutex_unlock(&fcoe_config_mutex);
+	return 0;
+
+out_free:
+	mutex_unlock(&fcoe_config_mutex);
+out_destroy:
+	destroy_workqueue(fcoe_wq);
+	return rc;
+}
+module_init(fcoe_init);
+
+/**
+ * fcoe_exit() - Clean up fcoe.ko
+ *
+ * Returns: 0 on success or a  negative value on failure
+ */
+static void __exit fcoe_exit(void)
+{
+	struct fcoe_interface *fcoe, *tmp;
+	struct fcoe_ctlr *ctlr;
+	struct fcoe_port *port;
+	unsigned int cpu;
+
+	mutex_lock(&fcoe_config_mutex);
+
+	fcoe_dev_cleanup();
+
+	/* releases the associated fcoe hosts */
+	rtnl_lock();
+	list_for_each_entry_safe(fcoe, tmp, &fcoe_hostlist, list) {
+		ctlr = fcoe_to_ctlr(fcoe);
+		port = lport_priv(ctlr->lp);
+		fcoe_hostlist_del(port->lport);
+		queue_work(fcoe_wq, &port->destroy_work);
+	}
+	rtnl_unlock();
+
+	for_each_possible_cpu(cpu)
+		fcoe_thread_cleanup_local(cpu);
+
+	mutex_unlock(&fcoe_config_mutex);
+
+	/*
+	 * destroy_work's may be chained but destroy_workqueue()
+	 * can take care of them. Just kill the fcoe_wq.
+	 */
+	destroy_workqueue(fcoe_wq);
+
+	/*
+	 * Detaching from the scsi transport must happen after all
+	 * destroys are done on the fcoe_wq. destroy_workqueue will
+	 * enusre the fcoe_wq is flushed.
+	 */
+	fcoe_if_exit();
+
+	/* detach from fcoe transport */
+	fcoe_transport_detach(&fcoe_sw_transport);
+}
+module_exit(fcoe_exit);
+
+/**
+ * fcoe_flogi_resp() - FCoE specific FLOGI and FDISC response handler
+ * @seq: active sequence in the FLOGI or FDISC exchange
+ * @fp: response frame, or error encoded in a pointer (timeout)
+ * @arg: pointer to the fcoe_ctlr structure
+ *
+ * This handles MAC address management for FCoE, then passes control on to
+ * the libfc FLOGI response handler.
+ */
+static void fcoe_flogi_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
+{
+	struct fcoe_ctlr *fip = arg;
+	struct fc_exch *exch = fc_seq_exch(seq);
+	struct fc_lport *lport = exch->lp;
+	u8 *mac;
+
+	if (IS_ERR(fp))
+		goto done;
+
+	mac = fr_cb(fp)->granted_mac;
+	/* pre-FIP */
+	if (is_zero_ether_addr(mac))
+		fcoe_ctlr_recv_flogi(fip, lport, fp);
+	if (!is_zero_ether_addr(mac))
+		fcoe_update_src_mac(lport, mac);
+done:
+	fc_lport_flogi_resp(seq, fp, lport);
+}
+
+/**
+ * fcoe_logo_resp() - FCoE specific LOGO response handler
+ * @seq: active sequence in the LOGO exchange
+ * @fp: response frame, or error encoded in a pointer (timeout)
+ * @arg: pointer to the fcoe_ctlr structure
+ *
+ * This handles MAC address management for FCoE, then passes control on to
+ * the libfc LOGO response handler.
+ */
+static void fcoe_logo_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
+{
+	struct fc_lport *lport = arg;
+	static u8 zero_mac[ETH_ALEN] = { 0 };
+
+	if (!IS_ERR(fp))
+		fcoe_update_src_mac(lport, zero_mac);
+	fc_lport_logo_resp(seq, fp, lport);
+}
+
+/**
+ * fcoe_elsct_send - FCoE specific ELS handler
+ *
+ * This does special case handling of FIP encapsualted ELS exchanges for FCoE,
+ * using FCoE specific response handlers and passing the FIP controller as
+ * the argument (the lport is still available from the exchange).
+ *
+ * Most of the work here is just handed off to the libfc routine.
+ */
+static struct fc_seq *fcoe_elsct_send(struct fc_lport *lport, u32 did,
+				      struct fc_frame *fp, unsigned int op,
+				      void (*resp)(struct fc_seq *,
+						   struct fc_frame *,
+						   void *),
+				      void *arg, u32 timeout)
+{
+	struct fcoe_port *port = lport_priv(lport);
+	struct fcoe_interface *fcoe = port->priv;
+	struct fcoe_ctlr *fip = fcoe_to_ctlr(fcoe);
+	struct fc_frame_header *fh = fc_frame_header_get(fp);
+
+	switch (op) {
+	case ELS_FLOGI:
+	case ELS_FDISC:
+		if (lport->point_to_multipoint)
+			break;
+		return fc_elsct_send(lport, did, fp, op, fcoe_flogi_resp,
+				     fip, timeout);
+	case ELS_LOGO:
+		/* only hook onto fabric logouts, not port logouts */
+		if (ntoh24(fh->fh_d_id) != FC_FID_FLOGI)
+			break;
+		return fc_elsct_send(lport, did, fp, op, fcoe_logo_resp,
+				     lport, timeout);
+	}
+	return fc_elsct_send(lport, did, fp, op, resp, arg, timeout);
+}
+
+/**
+ * fcoe_vport_create() - create an fc_host/scsi_host for a vport
+ * @vport: fc_vport object to create a new fc_host for
+ * @disabled: start the new fc_host in a disabled state by default?
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_vport_create(struct fc_vport *vport, bool disabled)
+{
+	struct Scsi_Host *shost = vport_to_shost(vport);
+	struct fc_lport *n_port = shost_priv(shost);
+	struct fcoe_port *port = lport_priv(n_port);
+	struct fcoe_interface *fcoe = port->priv;
+	struct net_device *netdev = fcoe->netdev;
+	struct fc_lport *vn_port;
+	int rc;
+	char buf[32];
+
+	rc = fcoe_validate_vport_create(vport);
+	if (rc) {
+		fcoe_wwn_to_str(vport->port_name, buf, sizeof(buf));
+		printk(KERN_ERR "fcoe: Failed to create vport, "
+			"WWPN (0x%s) already exists\n",
+			buf);
+		return rc;
+	}
+
+	mutex_lock(&fcoe_config_mutex);
+	rtnl_lock();
+	vn_port = fcoe_if_create(fcoe, &vport->dev, 1);
+	rtnl_unlock();
+	mutex_unlock(&fcoe_config_mutex);
+
+	if (IS_ERR(vn_port)) {
+		printk(KERN_ERR "fcoe: fcoe_vport_create(%s) failed\n",
+		       netdev->name);
+		return -EIO;
+	}
+
+	if (disabled) {
+		fc_vport_set_state(vport, FC_VPORT_DISABLED);
+	} else {
+		vn_port->boot_time = jiffies;
+		fc_fabric_login(vn_port);
+		fc_vport_setlink(vn_port);
+	}
+	return 0;
+}
+
+/**
+ * fcoe_vport_destroy() - destroy the fc_host/scsi_host for a vport
+ * @vport: fc_vport object that is being destroyed
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_vport_destroy(struct fc_vport *vport)
+{
+	struct Scsi_Host *shost = vport_to_shost(vport);
+	struct fc_lport *n_port = shost_priv(shost);
+	struct fc_lport *vn_port = vport->dd_data;
+
+	mutex_lock(&n_port->lp_mutex);
+	list_del(&vn_port->list);
+	mutex_unlock(&n_port->lp_mutex);
+
+	mutex_lock(&fcoe_config_mutex);
+	rtnl_lock();
+	fcoe_if_destroy(vn_port);
+	rtnl_unlock();
+	mutex_unlock(&fcoe_config_mutex);
+
+	return 0;
+}
+
+/**
+ * fcoe_vport_remove() - remove attached vports
+ * @lport: lport for which the vports should be removed
+ */
+static void fcoe_vport_remove(struct fc_lport *lport)
+{
+	struct Scsi_Host *shost;
+	struct fc_host_attrs *fc_host;
+	unsigned long flags;
+	struct fc_vport *vport;
+	struct fc_vport *next_vport;
+
+	shost = lport->host;
+	fc_host = shost_to_fc_host(shost);
+
+	/* Loop through all the vports and mark them for deletion */
+	spin_lock_irqsave(shost->host_lock, flags);
+	list_for_each_entry_safe(vport, next_vport, &fc_host->vports, peers) {
+		if (vport->flags & (FC_VPORT_DEL | FC_VPORT_CREATING)) {
+			continue;
+		} else {
+			vport->flags |= FC_VPORT_DELETING;
+			queue_work(fc_host_work_q(shost),
+				   &vport->vport_delete_work);
+		}
+	}
+	spin_unlock_irqrestore(shost->host_lock, flags);
+
+	flush_workqueue(fc_host_work_q(shost));
+}
+
+/**
+ * fcoe_vport_disable() - change vport state
+ * @vport: vport to bring online/offline
+ * @disable: should the vport be disabled?
+ */
+static int fcoe_vport_disable(struct fc_vport *vport, bool disable)
+{
+	struct fc_lport *lport = vport->dd_data;
+
+	if (disable) {
+		fc_vport_set_state(vport, FC_VPORT_DISABLED);
+		fc_fabric_logoff(lport);
+	} else {
+		lport->boot_time = jiffies;
+		fc_fabric_login(lport);
+		fc_vport_setlink(lport);
+	}
+
+	return 0;
+}
+
+/**
+ * fcoe_vport_set_symbolic_name() - append vport string to symbolic name
+ * @vport: fc_vport with a new symbolic name string
+ *
+ * After generating a new symbolic name string, a new RSPN_ID request is
+ * sent to the name server.  There is no response handler, so if it fails
+ * for some reason it will not be retried.
+ */
+static void fcoe_set_vport_symbolic_name(struct fc_vport *vport)
+{
+	struct fc_lport *lport = vport->dd_data;
+	struct fc_frame *fp;
+	size_t len;
+
+	snprintf(fc_host_symbolic_name(lport->host), FC_SYMBOLIC_NAME_SIZE,
+		 "%s v%s over %s : %s", FCOE_NAME, FCOE_VERSION,
+		 fcoe_netdev(lport)->name, vport->symbolic_name);
+
+	if (lport->state != LPORT_ST_READY)
+		return;
+
+	len = strnlen(fc_host_symbolic_name(lport->host), 255);
+	fp = fc_frame_alloc(lport,
+			    sizeof(struct fc_ct_hdr) +
+			    sizeof(struct fc_ns_rspn) + len);
+	if (!fp)
+		return;
+	lport->tt.elsct_send(lport, FC_FID_DIR_SERV, fp, FC_NS_RSPN_ID,
+			     NULL, NULL, 3 * lport->r_a_tov);
+}
+
+static void fcoe_fcf_get_vlan_id(struct fcoe_fcf_device *fcf_dev)
+{
+	struct fcoe_ctlr_device *ctlr_dev =
+		fcoe_fcf_dev_to_ctlr_dev(fcf_dev);
+	struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev);
+	struct fcoe_interface *fcoe = fcoe_ctlr_priv(ctlr);
+
+	fcf_dev->vlan_id = vlan_dev_vlan_id(fcoe->netdev);
+}
+
+/**
+ * fcoe_set_port_id() - Callback from libfc when Port_ID is set.
+ * @lport: the local port
+ * @port_id: the port ID
+ * @fp: the received frame, if any, that caused the port_id to be set.
+ *
+ * This routine handles the case where we received a FLOGI and are
+ * entering point-to-point mode.  We need to call fcoe_ctlr_recv_flogi()
+ * so it can set the non-mapped mode and gateway address.
+ *
+ * The FLOGI LS_ACC is handled by fcoe_flogi_resp().
+ */
+static void fcoe_set_port_id(struct fc_lport *lport,
+			     u32 port_id, struct fc_frame *fp)
+{
+	struct fcoe_port *port = lport_priv(lport);
+	struct fcoe_interface *fcoe = port->priv;
+	struct fcoe_ctlr *ctlr = fcoe_to_ctlr(fcoe);
+
+	if (fp && fc_frame_payload_op(fp) == ELS_FLOGI)
+		fcoe_ctlr_recv_flogi(ctlr, lport, fp);
+}
diff --git a/drivers/scsi/fcoe/fcoe.h b/drivers/scsi/fcoe/fcoe.h
new file mode 100644
index 0000000..6aa4820
--- /dev/null
+++ b/drivers/scsi/fcoe/fcoe.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright(c) 2009 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Maintained at www.Open-FCoE.org
+ */
+
+#ifndef _FCOE_H_
+#define _FCOE_H_
+
+#include <linux/skbuff.h>
+#include <linux/kthread.h>
+
+#define FCOE_MAX_QUEUE_DEPTH	256
+#define FCOE_MIN_QUEUE_DEPTH	32
+
+#define FCOE_WORD_TO_BYTE	4
+
+#define FCOE_VERSION	"0.1"
+#define FCOE_NAME	"fcoe"
+#define FCOE_VENDOR	"Open-FCoE.org"
+
+#define FCOE_MAX_LUN		0xFFFF
+#define FCOE_MAX_FCP_TARGET	256
+
+#define FCOE_MAX_OUTSTANDING_COMMANDS	1024
+
+#define FCOE_MIN_XID		0x0000	/* the min xid supported by fcoe_sw */
+#define FCOE_MAX_XID		0x0FFF	/* the max xid supported by fcoe_sw */
+
+extern unsigned int fcoe_debug_logging;
+
+#define FCOE_LOGGING	    0x01 /* General logging, not categorized */
+#define FCOE_NETDEV_LOGGING 0x02 /* Netdevice logging */
+
+#define FCOE_CHECK_LOGGING(LEVEL, CMD)					\
+do {                                                            	\
+	if (unlikely(fcoe_debug_logging & LEVEL))			\
+		do {							\
+			CMD;						\
+		} while (0);						\
+} while (0)
+
+#define FCOE_DBG(fmt, args...)						\
+	FCOE_CHECK_LOGGING(FCOE_LOGGING,				\
+			   pr_info("fcoe: " fmt, ##args);)
+
+#define FCOE_NETDEV_DBG(netdev, fmt, args...)			\
+	FCOE_CHECK_LOGGING(FCOE_NETDEV_LOGGING,			\
+			   pr_info("fcoe: %s: " fmt,		\
+				   netdev->name, ##args);)
+
+/**
+ * struct fcoe_interface - A FCoE interface
+ * @list:	      Handle for a list of FCoE interfaces
+ * @netdev:	      The associated net device
+ * @fcoe_packet_type: FCoE packet type
+ * @fip_packet_type:  FIP packet type
+ * @oem:	      The offload exchange manager for all local port
+ *		      instances associated with this port
+ * @removed:	      Indicates fcoe interface removed from net device
+ * @priority:	      Priority for the FCoE packet (DCB)
+ * This structure is 1:1 with a net device.
+ */
+struct fcoe_interface {
+	struct list_head   list;
+	struct net_device  *netdev;
+	struct net_device  *realdev;
+	struct packet_type fcoe_packet_type;
+	struct packet_type fip_packet_type;
+	struct packet_type fip_vlan_packet_type;
+	struct fc_exch_mgr *oem;
+	u8	removed;
+	u8	priority;
+};
+
+#define fcoe_to_ctlr(x)						\
+	(struct fcoe_ctlr *)(((struct fcoe_ctlr *)(x)) - 1)
+
+#define fcoe_from_ctlr(x)			\
+	((struct fcoe_interface *)((x) + 1))
+
+/**
+ * fcoe_netdev() - Return the net device associated with a local port
+ * @lport: The local port to get the net device from
+ */
+static inline struct net_device *fcoe_netdev(const struct fc_lport *lport)
+{
+	return ((struct fcoe_interface *)
+			((struct fcoe_port *)lport_priv(lport))->priv)->netdev;
+}
+
+#endif /* _FCOE_H_ */
diff --git a/drivers/scsi/fcoe/fcoe_ctlr.c b/drivers/scsi/fcoe/fcoe_ctlr.c
new file mode 100644
index 0000000..54da316
--- /dev/null
+++ b/drivers/scsi/fcoe/fcoe_ctlr.c
@@ -0,0 +1,3279 @@
+/*
+ * Copyright (c) 2008-2009 Cisco Systems, Inc.  All rights reserved.
+ * Copyright (c) 2009 Intel Corporation.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Maintained at www.Open-FCoE.org
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
+#include <linux/errno.h>
+#include <linux/bitops.h>
+#include <linux/slab.h>
+#include <net/rtnetlink.h>
+
+#include <scsi/fc/fc_els.h>
+#include <scsi/fc/fc_fs.h>
+#include <scsi/fc/fc_fip.h>
+#include <scsi/fc/fc_encaps.h>
+#include <scsi/fc/fc_fcoe.h>
+#include <scsi/fc/fc_fcp.h>
+
+#include <scsi/libfc.h>
+#include <scsi/libfcoe.h>
+
+#include "libfcoe.h"
+
+#define	FCOE_CTLR_MIN_FKA	500		/* min keep alive (mS) */
+#define	FCOE_CTLR_DEF_FKA	FIP_DEF_FKA	/* default keep alive (mS) */
+
+static void fcoe_ctlr_timeout(struct timer_list *);
+static void fcoe_ctlr_timer_work(struct work_struct *);
+static void fcoe_ctlr_recv_work(struct work_struct *);
+static int fcoe_ctlr_flogi_retry(struct fcoe_ctlr *);
+
+static void fcoe_ctlr_vn_start(struct fcoe_ctlr *);
+static int fcoe_ctlr_vn_recv(struct fcoe_ctlr *, struct sk_buff *);
+static void fcoe_ctlr_vn_timeout(struct fcoe_ctlr *);
+static int fcoe_ctlr_vn_lookup(struct fcoe_ctlr *, u32, u8 *);
+
+static int fcoe_ctlr_vlan_recv(struct fcoe_ctlr *, struct sk_buff *);
+
+static u8 fcoe_all_fcfs[ETH_ALEN] = FIP_ALL_FCF_MACS;
+static u8 fcoe_all_enode[ETH_ALEN] = FIP_ALL_ENODE_MACS;
+static u8 fcoe_all_vn2vn[ETH_ALEN] = FIP_ALL_VN2VN_MACS;
+static u8 fcoe_all_p2p[ETH_ALEN] = FIP_ALL_P2P_MACS;
+
+static const char * const fcoe_ctlr_states[] = {
+	[FIP_ST_DISABLED] =	"DISABLED",
+	[FIP_ST_LINK_WAIT] =	"LINK_WAIT",
+	[FIP_ST_AUTO] =		"AUTO",
+	[FIP_ST_NON_FIP] =	"NON_FIP",
+	[FIP_ST_ENABLED] =	"ENABLED",
+	[FIP_ST_VNMP_START] =	"VNMP_START",
+	[FIP_ST_VNMP_PROBE1] =	"VNMP_PROBE1",
+	[FIP_ST_VNMP_PROBE2] =	"VNMP_PROBE2",
+	[FIP_ST_VNMP_CLAIM] =	"VNMP_CLAIM",
+	[FIP_ST_VNMP_UP] =	"VNMP_UP",
+};
+
+static const char *fcoe_ctlr_state(enum fip_state state)
+{
+	const char *cp = "unknown";
+
+	if (state < ARRAY_SIZE(fcoe_ctlr_states))
+		cp = fcoe_ctlr_states[state];
+	if (!cp)
+		cp = "unknown";
+	return cp;
+}
+
+/**
+ * fcoe_ctlr_set_state() - Set and do debug printing for the new FIP state.
+ * @fip: The FCoE controller
+ * @state: The new state
+ */
+static void fcoe_ctlr_set_state(struct fcoe_ctlr *fip, enum fip_state state)
+{
+	if (state == fip->state)
+		return;
+	if (fip->lp)
+		LIBFCOE_FIP_DBG(fip, "state %s -> %s\n",
+			fcoe_ctlr_state(fip->state), fcoe_ctlr_state(state));
+	fip->state = state;
+}
+
+/**
+ * fcoe_ctlr_mtu_valid() - Check if a FCF's MTU is valid
+ * @fcf: The FCF to check
+ *
+ * Return non-zero if FCF fcoe_size has been validated.
+ */
+static inline int fcoe_ctlr_mtu_valid(const struct fcoe_fcf *fcf)
+{
+	return (fcf->flags & FIP_FL_SOL) != 0;
+}
+
+/**
+ * fcoe_ctlr_fcf_usable() - Check if a FCF is usable
+ * @fcf: The FCF to check
+ *
+ * Return non-zero if the FCF is usable.
+ */
+static inline int fcoe_ctlr_fcf_usable(struct fcoe_fcf *fcf)
+{
+	u16 flags = FIP_FL_SOL | FIP_FL_AVAIL;
+
+	return (fcf->flags & flags) == flags;
+}
+
+/**
+ * fcoe_ctlr_map_dest() - Set flag and OUI for mapping destination addresses
+ * @fip: The FCoE controller
+ */
+static void fcoe_ctlr_map_dest(struct fcoe_ctlr *fip)
+{
+	if (fip->mode == FIP_MODE_VN2VN)
+		hton24(fip->dest_addr, FIP_VN_FC_MAP);
+	else
+		hton24(fip->dest_addr, FIP_DEF_FC_MAP);
+	hton24(fip->dest_addr + 3, 0);
+	fip->map_dest = 1;
+}
+
+/**
+ * fcoe_ctlr_init() - Initialize the FCoE Controller instance
+ * @fip: The FCoE controller to initialize
+ */
+void fcoe_ctlr_init(struct fcoe_ctlr *fip, enum fip_state mode)
+{
+	fcoe_ctlr_set_state(fip, FIP_ST_LINK_WAIT);
+	fip->mode = mode;
+	fip->fip_resp = false;
+	INIT_LIST_HEAD(&fip->fcfs);
+	mutex_init(&fip->ctlr_mutex);
+	spin_lock_init(&fip->ctlr_lock);
+	fip->flogi_oxid = FC_XID_UNKNOWN;
+	timer_setup(&fip->timer, fcoe_ctlr_timeout, 0);
+	INIT_WORK(&fip->timer_work, fcoe_ctlr_timer_work);
+	INIT_WORK(&fip->recv_work, fcoe_ctlr_recv_work);
+	skb_queue_head_init(&fip->fip_recv_list);
+}
+EXPORT_SYMBOL(fcoe_ctlr_init);
+
+/**
+ * fcoe_sysfs_fcf_add() - Add a fcoe_fcf{,_device} to a fcoe_ctlr{,_device}
+ * @new: The newly discovered FCF
+ *
+ * Called with fip->ctlr_mutex held
+ */
+static int fcoe_sysfs_fcf_add(struct fcoe_fcf *new)
+{
+	struct fcoe_ctlr *fip = new->fip;
+	struct fcoe_ctlr_device *ctlr_dev;
+	struct fcoe_fcf_device *temp, *fcf_dev;
+	int rc = -ENOMEM;
+
+	LIBFCOE_FIP_DBG(fip, "New FCF fab %16.16llx mac %pM\n",
+			new->fabric_name, new->fcf_mac);
+
+	temp = kzalloc(sizeof(*temp), GFP_KERNEL);
+	if (!temp)
+		goto out;
+
+	temp->fabric_name = new->fabric_name;
+	temp->switch_name = new->switch_name;
+	temp->fc_map = new->fc_map;
+	temp->vfid = new->vfid;
+	memcpy(temp->mac, new->fcf_mac, ETH_ALEN);
+	temp->priority = new->pri;
+	temp->fka_period = new->fka_period;
+	temp->selected = 0; /* default to unselected */
+
+	/*
+	 * If ctlr_dev doesn't exist then it means we're a libfcoe user
+	 * who doesn't use fcoe_syfs and didn't allocate a fcoe_ctlr_device.
+	 * fnic would be an example of a driver with this behavior. In this
+	 * case we want to add the fcoe_fcf to the fcoe_ctlr list, but we
+	 * don't want to make sysfs changes.
+	 */
+
+	ctlr_dev = fcoe_ctlr_to_ctlr_dev(fip);
+	if (ctlr_dev) {
+		mutex_lock(&ctlr_dev->lock);
+		fcf_dev = fcoe_fcf_device_add(ctlr_dev, temp);
+		if (unlikely(!fcf_dev)) {
+			rc = -ENOMEM;
+			mutex_unlock(&ctlr_dev->lock);
+			goto out;
+		}
+
+		/*
+		 * The fcoe_sysfs layer can return a CONNECTED fcf that
+		 * has a priv (fcf was never deleted) or a CONNECTED fcf
+		 * that doesn't have a priv (fcf was deleted). However,
+		 * libfcoe will always delete FCFs before trying to add
+		 * them. This is ensured because both recv_adv and
+		 * age_fcfs are protected by the the fcoe_ctlr's mutex.
+		 * This means that we should never get a FCF with a
+		 * non-NULL priv pointer.
+		 */
+		BUG_ON(fcf_dev->priv);
+
+		fcf_dev->priv = new;
+		new->fcf_dev = fcf_dev;
+		mutex_unlock(&ctlr_dev->lock);
+	}
+
+	list_add(&new->list, &fip->fcfs);
+	fip->fcf_count++;
+	rc = 0;
+
+out:
+	kfree(temp);
+	return rc;
+}
+
+/**
+ * fcoe_sysfs_fcf_del() - Remove a fcoe_fcf{,_device} to a fcoe_ctlr{,_device}
+ * @new: The FCF to be removed
+ *
+ * Called with fip->ctlr_mutex held
+ */
+static void fcoe_sysfs_fcf_del(struct fcoe_fcf *new)
+{
+	struct fcoe_ctlr *fip = new->fip;
+	struct fcoe_ctlr_device *cdev;
+	struct fcoe_fcf_device *fcf_dev;
+
+	list_del(&new->list);
+	fip->fcf_count--;
+
+	/*
+	 * If ctlr_dev doesn't exist then it means we're a libfcoe user
+	 * who doesn't use fcoe_syfs and didn't allocate a fcoe_ctlr_device
+	 * or a fcoe_fcf_device.
+	 *
+	 * fnic would be an example of a driver with this behavior. In this
+	 * case we want to remove the fcoe_fcf from the fcoe_ctlr list (above),
+	 * but we don't want to make sysfs changes.
+	 */
+	cdev = fcoe_ctlr_to_ctlr_dev(fip);
+	if (cdev) {
+		mutex_lock(&cdev->lock);
+		fcf_dev = fcoe_fcf_to_fcf_dev(new);
+		WARN_ON(!fcf_dev);
+		new->fcf_dev = NULL;
+		fcoe_fcf_device_delete(fcf_dev);
+		kfree(new);
+		mutex_unlock(&cdev->lock);
+	}
+}
+
+/**
+ * fcoe_ctlr_reset_fcfs() - Reset and free all FCFs for a controller
+ * @fip: The FCoE controller whose FCFs are to be reset
+ *
+ * Called with &fcoe_ctlr lock held.
+ */
+static void fcoe_ctlr_reset_fcfs(struct fcoe_ctlr *fip)
+{
+	struct fcoe_fcf *fcf;
+	struct fcoe_fcf *next;
+
+	fip->sel_fcf = NULL;
+	list_for_each_entry_safe(fcf, next, &fip->fcfs, list) {
+		fcoe_sysfs_fcf_del(fcf);
+	}
+	WARN_ON(fip->fcf_count);
+
+	fip->sel_time = 0;
+}
+
+/**
+ * fcoe_ctlr_destroy() - Disable and tear down a FCoE controller
+ * @fip: The FCoE controller to tear down
+ *
+ * This is called by FCoE drivers before freeing the &fcoe_ctlr.
+ *
+ * The receive handler will have been deleted before this to guarantee
+ * that no more recv_work will be scheduled.
+ *
+ * The timer routine will simply return once we set FIP_ST_DISABLED.
+ * This guarantees that no further timeouts or work will be scheduled.
+ */
+void fcoe_ctlr_destroy(struct fcoe_ctlr *fip)
+{
+	cancel_work_sync(&fip->recv_work);
+	skb_queue_purge(&fip->fip_recv_list);
+
+	mutex_lock(&fip->ctlr_mutex);
+	fcoe_ctlr_set_state(fip, FIP_ST_DISABLED);
+	fcoe_ctlr_reset_fcfs(fip);
+	mutex_unlock(&fip->ctlr_mutex);
+	del_timer_sync(&fip->timer);
+	cancel_work_sync(&fip->timer_work);
+}
+EXPORT_SYMBOL(fcoe_ctlr_destroy);
+
+/**
+ * fcoe_ctlr_announce() - announce new FCF selection
+ * @fip: The FCoE controller
+ *
+ * Also sets the destination MAC for FCoE and control packets
+ *
+ * Called with neither ctlr_mutex nor ctlr_lock held.
+ */
+static void fcoe_ctlr_announce(struct fcoe_ctlr *fip)
+{
+	struct fcoe_fcf *sel;
+	struct fcoe_fcf *fcf;
+
+	mutex_lock(&fip->ctlr_mutex);
+	spin_lock_bh(&fip->ctlr_lock);
+
+	kfree_skb(fip->flogi_req);
+	fip->flogi_req = NULL;
+	list_for_each_entry(fcf, &fip->fcfs, list)
+		fcf->flogi_sent = 0;
+
+	spin_unlock_bh(&fip->ctlr_lock);
+	sel = fip->sel_fcf;
+
+	if (sel && ether_addr_equal(sel->fcf_mac, fip->dest_addr))
+		goto unlock;
+	if (!is_zero_ether_addr(fip->dest_addr)) {
+		printk(KERN_NOTICE "libfcoe: host%d: "
+		       "FIP Fibre-Channel Forwarder MAC %pM deselected\n",
+		       fip->lp->host->host_no, fip->dest_addr);
+		memset(fip->dest_addr, 0, ETH_ALEN);
+	}
+	if (sel) {
+		printk(KERN_INFO "libfcoe: host%d: FIP selected "
+		       "Fibre-Channel Forwarder MAC %pM\n",
+		       fip->lp->host->host_no, sel->fcf_mac);
+		memcpy(fip->dest_addr, sel->fcoe_mac, ETH_ALEN);
+		fip->map_dest = 0;
+	}
+unlock:
+	mutex_unlock(&fip->ctlr_mutex);
+}
+
+/**
+ * fcoe_ctlr_fcoe_size() - Return the maximum FCoE size required for VN_Port
+ * @fip: The FCoE controller to get the maximum FCoE size from
+ *
+ * Returns the maximum packet size including the FCoE header and trailer,
+ * but not including any Ethernet or VLAN headers.
+ */
+static inline u32 fcoe_ctlr_fcoe_size(struct fcoe_ctlr *fip)
+{
+	/*
+	 * Determine the max FCoE frame size allowed, including
+	 * FCoE header and trailer.
+	 * Note:  lp->mfs is currently the payload size, not the frame size.
+	 */
+	return fip->lp->mfs + sizeof(struct fc_frame_header) +
+		sizeof(struct fcoe_hdr) + sizeof(struct fcoe_crc_eof);
+}
+
+/**
+ * fcoe_ctlr_solicit() - Send a FIP solicitation
+ * @fip: The FCoE controller to send the solicitation on
+ * @fcf: The destination FCF (if NULL, a multicast solicitation is sent)
+ */
+static void fcoe_ctlr_solicit(struct fcoe_ctlr *fip, struct fcoe_fcf *fcf)
+{
+	struct sk_buff *skb;
+	struct fip_sol {
+		struct ethhdr eth;
+		struct fip_header fip;
+		struct {
+			struct fip_mac_desc mac;
+			struct fip_wwn_desc wwnn;
+			struct fip_size_desc size;
+		} __packed desc;
+	}  __packed * sol;
+	u32 fcoe_size;
+
+	skb = dev_alloc_skb(sizeof(*sol));
+	if (!skb)
+		return;
+
+	sol = (struct fip_sol *)skb->data;
+
+	memset(sol, 0, sizeof(*sol));
+	memcpy(sol->eth.h_dest, fcf ? fcf->fcf_mac : fcoe_all_fcfs, ETH_ALEN);
+	memcpy(sol->eth.h_source, fip->ctl_src_addr, ETH_ALEN);
+	sol->eth.h_proto = htons(ETH_P_FIP);
+
+	sol->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER);
+	sol->fip.fip_op = htons(FIP_OP_DISC);
+	sol->fip.fip_subcode = FIP_SC_SOL;
+	sol->fip.fip_dl_len = htons(sizeof(sol->desc) / FIP_BPW);
+	sol->fip.fip_flags = htons(FIP_FL_FPMA);
+	if (fip->spma)
+		sol->fip.fip_flags |= htons(FIP_FL_SPMA);
+
+	sol->desc.mac.fd_desc.fip_dtype = FIP_DT_MAC;
+	sol->desc.mac.fd_desc.fip_dlen = sizeof(sol->desc.mac) / FIP_BPW;
+	memcpy(sol->desc.mac.fd_mac, fip->ctl_src_addr, ETH_ALEN);
+
+	sol->desc.wwnn.fd_desc.fip_dtype = FIP_DT_NAME;
+	sol->desc.wwnn.fd_desc.fip_dlen = sizeof(sol->desc.wwnn) / FIP_BPW;
+	put_unaligned_be64(fip->lp->wwnn, &sol->desc.wwnn.fd_wwn);
+
+	fcoe_size = fcoe_ctlr_fcoe_size(fip);
+	sol->desc.size.fd_desc.fip_dtype = FIP_DT_FCOE_SIZE;
+	sol->desc.size.fd_desc.fip_dlen = sizeof(sol->desc.size) / FIP_BPW;
+	sol->desc.size.fd_size = htons(fcoe_size);
+
+	skb_put(skb, sizeof(*sol));
+	skb->protocol = htons(ETH_P_FIP);
+	skb->priority = fip->priority;
+	skb_reset_mac_header(skb);
+	skb_reset_network_header(skb);
+	fip->send(fip, skb);
+
+	if (!fcf)
+		fip->sol_time = jiffies;
+}
+
+/**
+ * fcoe_ctlr_link_up() - Start FCoE controller
+ * @fip: The FCoE controller to start
+ *
+ * Called from the LLD when the network link is ready.
+ */
+void fcoe_ctlr_link_up(struct fcoe_ctlr *fip)
+{
+	mutex_lock(&fip->ctlr_mutex);
+	if (fip->state == FIP_ST_NON_FIP || fip->state == FIP_ST_AUTO) {
+		mutex_unlock(&fip->ctlr_mutex);
+		fc_linkup(fip->lp);
+	} else if (fip->state == FIP_ST_LINK_WAIT) {
+		fcoe_ctlr_set_state(fip, fip->mode);
+		switch (fip->mode) {
+		default:
+			LIBFCOE_FIP_DBG(fip, "invalid mode %d\n", fip->mode);
+			/* fall-through */
+		case FIP_MODE_AUTO:
+			LIBFCOE_FIP_DBG(fip, "%s", "setting AUTO mode.\n");
+			/* fall-through */
+		case FIP_MODE_FABRIC:
+		case FIP_MODE_NON_FIP:
+			mutex_unlock(&fip->ctlr_mutex);
+			fc_linkup(fip->lp);
+			fcoe_ctlr_solicit(fip, NULL);
+			break;
+		case FIP_MODE_VN2VN:
+			fcoe_ctlr_vn_start(fip);
+			mutex_unlock(&fip->ctlr_mutex);
+			fc_linkup(fip->lp);
+			break;
+		}
+	} else
+		mutex_unlock(&fip->ctlr_mutex);
+}
+EXPORT_SYMBOL(fcoe_ctlr_link_up);
+
+/**
+ * fcoe_ctlr_reset() - Reset a FCoE controller
+ * @fip:       The FCoE controller to reset
+ */
+static void fcoe_ctlr_reset(struct fcoe_ctlr *fip)
+{
+	fcoe_ctlr_reset_fcfs(fip);
+	del_timer(&fip->timer);
+	fip->ctlr_ka_time = 0;
+	fip->port_ka_time = 0;
+	fip->sol_time = 0;
+	fip->flogi_oxid = FC_XID_UNKNOWN;
+	fcoe_ctlr_map_dest(fip);
+}
+
+/**
+ * fcoe_ctlr_link_down() - Stop a FCoE controller
+ * @fip: The FCoE controller to be stopped
+ *
+ * Returns non-zero if the link was up and now isn't.
+ *
+ * Called from the LLD when the network link is not ready.
+ * There may be multiple calls while the link is down.
+ */
+int fcoe_ctlr_link_down(struct fcoe_ctlr *fip)
+{
+	int link_dropped;
+
+	LIBFCOE_FIP_DBG(fip, "link down.\n");
+	mutex_lock(&fip->ctlr_mutex);
+	fcoe_ctlr_reset(fip);
+	link_dropped = fip->state != FIP_ST_LINK_WAIT;
+	fcoe_ctlr_set_state(fip, FIP_ST_LINK_WAIT);
+	mutex_unlock(&fip->ctlr_mutex);
+
+	if (link_dropped)
+		fc_linkdown(fip->lp);
+	return link_dropped;
+}
+EXPORT_SYMBOL(fcoe_ctlr_link_down);
+
+/**
+ * fcoe_ctlr_send_keep_alive() - Send a keep-alive to the selected FCF
+ * @fip:   The FCoE controller to send the FKA on
+ * @lport: libfc fc_lport to send from
+ * @ports: 0 for controller keep-alive, 1 for port keep-alive
+ * @sa:	   The source MAC address
+ *
+ * A controller keep-alive is sent every fka_period (typically 8 seconds).
+ * The source MAC is the native MAC address.
+ *
+ * A port keep-alive is sent every 90 seconds while logged in.
+ * The source MAC is the assigned mapped source address.
+ * The destination is the FCF's F-port.
+ */
+static void fcoe_ctlr_send_keep_alive(struct fcoe_ctlr *fip,
+				      struct fc_lport *lport,
+				      int ports, u8 *sa)
+{
+	struct sk_buff *skb;
+	struct fip_kal {
+		struct ethhdr eth;
+		struct fip_header fip;
+		struct fip_mac_desc mac;
+	} __packed * kal;
+	struct fip_vn_desc *vn;
+	u32 len;
+	struct fc_lport *lp;
+	struct fcoe_fcf *fcf;
+
+	fcf = fip->sel_fcf;
+	lp = fip->lp;
+	if (!fcf || (ports && !lp->port_id))
+		return;
+
+	len = sizeof(*kal) + ports * sizeof(*vn);
+	skb = dev_alloc_skb(len);
+	if (!skb)
+		return;
+
+	kal = (struct fip_kal *)skb->data;
+	memset(kal, 0, len);
+	memcpy(kal->eth.h_dest, fcf->fcf_mac, ETH_ALEN);
+	memcpy(kal->eth.h_source, sa, ETH_ALEN);
+	kal->eth.h_proto = htons(ETH_P_FIP);
+
+	kal->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER);
+	kal->fip.fip_op = htons(FIP_OP_CTRL);
+	kal->fip.fip_subcode = FIP_SC_KEEP_ALIVE;
+	kal->fip.fip_dl_len = htons((sizeof(kal->mac) +
+				     ports * sizeof(*vn)) / FIP_BPW);
+	kal->fip.fip_flags = htons(FIP_FL_FPMA);
+	if (fip->spma)
+		kal->fip.fip_flags |= htons(FIP_FL_SPMA);
+
+	kal->mac.fd_desc.fip_dtype = FIP_DT_MAC;
+	kal->mac.fd_desc.fip_dlen = sizeof(kal->mac) / FIP_BPW;
+	memcpy(kal->mac.fd_mac, fip->ctl_src_addr, ETH_ALEN);
+	if (ports) {
+		vn = (struct fip_vn_desc *)(kal + 1);
+		vn->fd_desc.fip_dtype = FIP_DT_VN_ID;
+		vn->fd_desc.fip_dlen = sizeof(*vn) / FIP_BPW;
+		memcpy(vn->fd_mac, fip->get_src_addr(lport), ETH_ALEN);
+		hton24(vn->fd_fc_id, lport->port_id);
+		put_unaligned_be64(lport->wwpn, &vn->fd_wwpn);
+	}
+	skb_put(skb, len);
+	skb->protocol = htons(ETH_P_FIP);
+	skb->priority = fip->priority;
+	skb_reset_mac_header(skb);
+	skb_reset_network_header(skb);
+	fip->send(fip, skb);
+}
+
+/**
+ * fcoe_ctlr_encaps() - Encapsulate an ELS frame for FIP, without sending it
+ * @fip:   The FCoE controller for the ELS frame
+ * @dtype: The FIP descriptor type for the frame
+ * @skb:   The FCoE ELS frame including FC header but no FCoE headers
+ * @d_id:  The destination port ID.
+ *
+ * Returns non-zero error code on failure.
+ *
+ * The caller must check that the length is a multiple of 4.
+ *
+ * The @skb must have enough headroom (28 bytes) and tailroom (8 bytes).
+ * Headroom includes the FIP encapsulation description, FIP header, and
+ * Ethernet header.  The tailroom is for the FIP MAC descriptor.
+ */
+static int fcoe_ctlr_encaps(struct fcoe_ctlr *fip, struct fc_lport *lport,
+			    u8 dtype, struct sk_buff *skb, u32 d_id)
+{
+	struct fip_encaps_head {
+		struct ethhdr eth;
+		struct fip_header fip;
+		struct fip_encaps encaps;
+	} __packed * cap;
+	struct fc_frame_header *fh;
+	struct fip_mac_desc *mac;
+	struct fcoe_fcf *fcf;
+	size_t dlen;
+	u16 fip_flags;
+	u8 op;
+
+	fh = (struct fc_frame_header *)skb->data;
+	op = *(u8 *)(fh + 1);
+	dlen = sizeof(struct fip_encaps) + skb->len;	/* len before push */
+	cap = skb_push(skb, sizeof(*cap));
+	memset(cap, 0, sizeof(*cap));
+
+	if (lport->point_to_multipoint) {
+		if (fcoe_ctlr_vn_lookup(fip, d_id, cap->eth.h_dest))
+			return -ENODEV;
+		fip_flags = 0;
+	} else {
+		fcf = fip->sel_fcf;
+		if (!fcf)
+			return -ENODEV;
+		fip_flags = fcf->flags;
+		fip_flags &= fip->spma ? FIP_FL_SPMA | FIP_FL_FPMA :
+					 FIP_FL_FPMA;
+		if (!fip_flags)
+			return -ENODEV;
+		memcpy(cap->eth.h_dest, fcf->fcf_mac, ETH_ALEN);
+	}
+	memcpy(cap->eth.h_source, fip->ctl_src_addr, ETH_ALEN);
+	cap->eth.h_proto = htons(ETH_P_FIP);
+
+	cap->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER);
+	cap->fip.fip_op = htons(FIP_OP_LS);
+	if (op == ELS_LS_ACC || op == ELS_LS_RJT)
+		cap->fip.fip_subcode = FIP_SC_REP;
+	else
+		cap->fip.fip_subcode = FIP_SC_REQ;
+	cap->fip.fip_flags = htons(fip_flags);
+
+	cap->encaps.fd_desc.fip_dtype = dtype;
+	cap->encaps.fd_desc.fip_dlen = dlen / FIP_BPW;
+
+	if (op != ELS_LS_RJT) {
+		dlen += sizeof(*mac);
+		mac = skb_put_zero(skb, sizeof(*mac));
+		mac->fd_desc.fip_dtype = FIP_DT_MAC;
+		mac->fd_desc.fip_dlen = sizeof(*mac) / FIP_BPW;
+		if (dtype != FIP_DT_FLOGI && dtype != FIP_DT_FDISC) {
+			memcpy(mac->fd_mac, fip->get_src_addr(lport), ETH_ALEN);
+		} else if (fip->mode == FIP_MODE_VN2VN) {
+			hton24(mac->fd_mac, FIP_VN_FC_MAP);
+			hton24(mac->fd_mac + 3, fip->port_id);
+		} else if (fip_flags & FIP_FL_SPMA) {
+			LIBFCOE_FIP_DBG(fip, "FLOGI/FDISC sent with SPMA\n");
+			memcpy(mac->fd_mac, fip->ctl_src_addr, ETH_ALEN);
+		} else {
+			LIBFCOE_FIP_DBG(fip, "FLOGI/FDISC sent with FPMA\n");
+			/* FPMA only FLOGI.  Must leave the MAC desc zeroed. */
+		}
+	}
+	cap->fip.fip_dl_len = htons(dlen / FIP_BPW);
+
+	skb->protocol = htons(ETH_P_FIP);
+	skb->priority = fip->priority;
+	skb_reset_mac_header(skb);
+	skb_reset_network_header(skb);
+	return 0;
+}
+
+/**
+ * fcoe_ctlr_els_send() - Send an ELS frame encapsulated by FIP if appropriate.
+ * @fip:	FCoE controller.
+ * @lport:	libfc fc_lport to send from
+ * @skb:	FCoE ELS frame including FC header but no FCoE headers.
+ *
+ * Returns a non-zero error code if the frame should not be sent.
+ * Returns zero if the caller should send the frame with FCoE encapsulation.
+ *
+ * The caller must check that the length is a multiple of 4.
+ * The SKB must have enough headroom (28 bytes) and tailroom (8 bytes).
+ * The the skb must also be an fc_frame.
+ *
+ * This is called from the lower-level driver with spinlocks held,
+ * so we must not take a mutex here.
+ */
+int fcoe_ctlr_els_send(struct fcoe_ctlr *fip, struct fc_lport *lport,
+		       struct sk_buff *skb)
+{
+	struct fc_frame *fp;
+	struct fc_frame_header *fh;
+	u16 old_xid;
+	u8 op;
+	u8 mac[ETH_ALEN];
+
+	fp = container_of(skb, struct fc_frame, skb);
+	fh = (struct fc_frame_header *)skb->data;
+	op = *(u8 *)(fh + 1);
+
+	if (op == ELS_FLOGI && fip->mode != FIP_MODE_VN2VN) {
+		old_xid = fip->flogi_oxid;
+		fip->flogi_oxid = ntohs(fh->fh_ox_id);
+		if (fip->state == FIP_ST_AUTO) {
+			if (old_xid == FC_XID_UNKNOWN)
+				fip->flogi_count = 0;
+			fip->flogi_count++;
+			if (fip->flogi_count < 3)
+				goto drop;
+			fcoe_ctlr_map_dest(fip);
+			return 0;
+		}
+		if (fip->state == FIP_ST_NON_FIP)
+			fcoe_ctlr_map_dest(fip);
+	}
+
+	if (fip->state == FIP_ST_NON_FIP)
+		return 0;
+	if (!fip->sel_fcf && fip->mode != FIP_MODE_VN2VN)
+		goto drop;
+	switch (op) {
+	case ELS_FLOGI:
+		op = FIP_DT_FLOGI;
+		if (fip->mode == FIP_MODE_VN2VN)
+			break;
+		spin_lock_bh(&fip->ctlr_lock);
+		kfree_skb(fip->flogi_req);
+		fip->flogi_req = skb;
+		fip->flogi_req_send = 1;
+		spin_unlock_bh(&fip->ctlr_lock);
+		schedule_work(&fip->timer_work);
+		return -EINPROGRESS;
+	case ELS_FDISC:
+		if (ntoh24(fh->fh_s_id))
+			return 0;
+		op = FIP_DT_FDISC;
+		break;
+	case ELS_LOGO:
+		if (fip->mode == FIP_MODE_VN2VN) {
+			if (fip->state != FIP_ST_VNMP_UP)
+				goto drop;
+			if (ntoh24(fh->fh_d_id) == FC_FID_FLOGI)
+				goto drop;
+		} else {
+			if (fip->state != FIP_ST_ENABLED)
+				return 0;
+			if (ntoh24(fh->fh_d_id) != FC_FID_FLOGI)
+				return 0;
+		}
+		op = FIP_DT_LOGO;
+		break;
+	case ELS_LS_ACC:
+		/*
+		 * If non-FIP, we may have gotten an SID by accepting an FLOGI
+		 * from a point-to-point connection.  Switch to using
+		 * the source mac based on the SID.  The destination
+		 * MAC in this case would have been set by receiving the
+		 * FLOGI.
+		 */
+		if (fip->state == FIP_ST_NON_FIP) {
+			if (fip->flogi_oxid == FC_XID_UNKNOWN)
+				return 0;
+			fip->flogi_oxid = FC_XID_UNKNOWN;
+			fc_fcoe_set_mac(mac, fh->fh_d_id);
+			fip->update_mac(lport, mac);
+		}
+		/* fall through */
+	case ELS_LS_RJT:
+		op = fr_encaps(fp);
+		if (op)
+			break;
+		return 0;
+	default:
+		if (fip->state != FIP_ST_ENABLED &&
+		    fip->state != FIP_ST_VNMP_UP)
+			goto drop;
+		return 0;
+	}
+	LIBFCOE_FIP_DBG(fip, "els_send op %u d_id %x\n",
+			op, ntoh24(fh->fh_d_id));
+	if (fcoe_ctlr_encaps(fip, lport, op, skb, ntoh24(fh->fh_d_id)))
+		goto drop;
+	fip->send(fip, skb);
+	return -EINPROGRESS;
+drop:
+	LIBFCOE_FIP_DBG(fip, "drop els_send op %u d_id %x\n",
+			op, ntoh24(fh->fh_d_id));
+	kfree_skb(skb);
+	return -EINVAL;
+}
+EXPORT_SYMBOL(fcoe_ctlr_els_send);
+
+/**
+ * fcoe_ctlr_age_fcfs() - Reset and free all old FCFs for a controller
+ * @fip: The FCoE controller to free FCFs on
+ *
+ * Called with lock held and preemption disabled.
+ *
+ * An FCF is considered old if we have missed two advertisements.
+ * That is, there have been no valid advertisement from it for 2.5
+ * times its keep-alive period.
+ *
+ * In addition, determine the time when an FCF selection can occur.
+ *
+ * Also, increment the MissDiscAdvCount when no advertisement is received
+ * for the corresponding FCF for 1.5 * FKA_ADV_PERIOD (FC-BB-5 LESB).
+ *
+ * Returns the time in jiffies for the next call.
+ */
+static unsigned long fcoe_ctlr_age_fcfs(struct fcoe_ctlr *fip)
+{
+	struct fcoe_fcf *fcf;
+	struct fcoe_fcf *next;
+	unsigned long next_timer = jiffies + msecs_to_jiffies(FIP_VN_KA_PERIOD);
+	unsigned long deadline;
+	unsigned long sel_time = 0;
+	struct list_head del_list;
+	struct fc_stats *stats;
+
+	INIT_LIST_HEAD(&del_list);
+
+	stats = per_cpu_ptr(fip->lp->stats, get_cpu());
+
+	list_for_each_entry_safe(fcf, next, &fip->fcfs, list) {
+		deadline = fcf->time + fcf->fka_period + fcf->fka_period / 2;
+		if (fip->sel_fcf == fcf) {
+			if (time_after(jiffies, deadline)) {
+				stats->MissDiscAdvCount++;
+				printk(KERN_INFO "libfcoe: host%d: "
+				       "Missing Discovery Advertisement "
+				       "for fab %16.16llx count %lld\n",
+				       fip->lp->host->host_no, fcf->fabric_name,
+				       stats->MissDiscAdvCount);
+			} else if (time_after(next_timer, deadline))
+				next_timer = deadline;
+		}
+
+		deadline += fcf->fka_period;
+		if (time_after_eq(jiffies, deadline)) {
+			if (fip->sel_fcf == fcf)
+				fip->sel_fcf = NULL;
+			/*
+			 * Move to delete list so we can call
+			 * fcoe_sysfs_fcf_del (which can sleep)
+			 * after the put_cpu().
+			 */
+			list_del(&fcf->list);
+			list_add(&fcf->list, &del_list);
+			stats->VLinkFailureCount++;
+		} else {
+			if (time_after(next_timer, deadline))
+				next_timer = deadline;
+			if (fcoe_ctlr_mtu_valid(fcf) &&
+			    (!sel_time || time_before(sel_time, fcf->time)))
+				sel_time = fcf->time;
+		}
+	}
+	put_cpu();
+
+	list_for_each_entry_safe(fcf, next, &del_list, list) {
+		/* Removes fcf from current list */
+		fcoe_sysfs_fcf_del(fcf);
+	}
+
+	if (sel_time && !fip->sel_fcf && !fip->sel_time) {
+		sel_time += msecs_to_jiffies(FCOE_CTLR_START_DELAY);
+		fip->sel_time = sel_time;
+	}
+
+	return next_timer;
+}
+
+/**
+ * fcoe_ctlr_parse_adv() - Decode a FIP advertisement into a new FCF entry
+ * @fip: The FCoE controller receiving the advertisement
+ * @skb: The received FIP advertisement frame
+ * @fcf: The resulting FCF entry
+ *
+ * Returns zero on a valid parsed advertisement,
+ * otherwise returns non zero value.
+ */
+static int fcoe_ctlr_parse_adv(struct fcoe_ctlr *fip,
+			       struct sk_buff *skb, struct fcoe_fcf *fcf)
+{
+	struct fip_header *fiph;
+	struct fip_desc *desc = NULL;
+	struct fip_wwn_desc *wwn;
+	struct fip_fab_desc *fab;
+	struct fip_fka_desc *fka;
+	unsigned long t;
+	size_t rlen;
+	size_t dlen;
+	u32 desc_mask;
+
+	memset(fcf, 0, sizeof(*fcf));
+	fcf->fka_period = msecs_to_jiffies(FCOE_CTLR_DEF_FKA);
+
+	fiph = (struct fip_header *)skb->data;
+	fcf->flags = ntohs(fiph->fip_flags);
+
+	/*
+	 * mask of required descriptors. validating each one clears its bit.
+	 */
+	desc_mask = BIT(FIP_DT_PRI) | BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME) |
+			BIT(FIP_DT_FAB) | BIT(FIP_DT_FKA);
+
+	rlen = ntohs(fiph->fip_dl_len) * 4;
+	if (rlen + sizeof(*fiph) > skb->len)
+		return -EINVAL;
+
+	desc = (struct fip_desc *)(fiph + 1);
+	while (rlen > 0) {
+		dlen = desc->fip_dlen * FIP_BPW;
+		if (dlen < sizeof(*desc) || dlen > rlen)
+			return -EINVAL;
+		/* Drop Adv if there are duplicate critical descriptors */
+		if ((desc->fip_dtype < 32) &&
+		    !(desc_mask & 1U << desc->fip_dtype)) {
+			LIBFCOE_FIP_DBG(fip, "Duplicate Critical "
+					"Descriptors in FIP adv\n");
+			return -EINVAL;
+		}
+		switch (desc->fip_dtype) {
+		case FIP_DT_PRI:
+			if (dlen != sizeof(struct fip_pri_desc))
+				goto len_err;
+			fcf->pri = ((struct fip_pri_desc *)desc)->fd_pri;
+			desc_mask &= ~BIT(FIP_DT_PRI);
+			break;
+		case FIP_DT_MAC:
+			if (dlen != sizeof(struct fip_mac_desc))
+				goto len_err;
+			memcpy(fcf->fcf_mac,
+			       ((struct fip_mac_desc *)desc)->fd_mac,
+			       ETH_ALEN);
+			memcpy(fcf->fcoe_mac, fcf->fcf_mac, ETH_ALEN);
+			if (!is_valid_ether_addr(fcf->fcf_mac)) {
+				LIBFCOE_FIP_DBG(fip,
+					"Invalid MAC addr %pM in FIP adv\n",
+					fcf->fcf_mac);
+				return -EINVAL;
+			}
+			desc_mask &= ~BIT(FIP_DT_MAC);
+			break;
+		case FIP_DT_NAME:
+			if (dlen != sizeof(struct fip_wwn_desc))
+				goto len_err;
+			wwn = (struct fip_wwn_desc *)desc;
+			fcf->switch_name = get_unaligned_be64(&wwn->fd_wwn);
+			desc_mask &= ~BIT(FIP_DT_NAME);
+			break;
+		case FIP_DT_FAB:
+			if (dlen != sizeof(struct fip_fab_desc))
+				goto len_err;
+			fab = (struct fip_fab_desc *)desc;
+			fcf->fabric_name = get_unaligned_be64(&fab->fd_wwn);
+			fcf->vfid = ntohs(fab->fd_vfid);
+			fcf->fc_map = ntoh24(fab->fd_map);
+			desc_mask &= ~BIT(FIP_DT_FAB);
+			break;
+		case FIP_DT_FKA:
+			if (dlen != sizeof(struct fip_fka_desc))
+				goto len_err;
+			fka = (struct fip_fka_desc *)desc;
+			if (fka->fd_flags & FIP_FKA_ADV_D)
+				fcf->fd_flags = 1;
+			t = ntohl(fka->fd_fka_period);
+			if (t >= FCOE_CTLR_MIN_FKA)
+				fcf->fka_period = msecs_to_jiffies(t);
+			desc_mask &= ~BIT(FIP_DT_FKA);
+			break;
+		case FIP_DT_MAP_OUI:
+		case FIP_DT_FCOE_SIZE:
+		case FIP_DT_FLOGI:
+		case FIP_DT_FDISC:
+		case FIP_DT_LOGO:
+		case FIP_DT_ELP:
+		default:
+			LIBFCOE_FIP_DBG(fip, "unexpected descriptor type %x "
+					"in FIP adv\n", desc->fip_dtype);
+			/* standard says ignore unknown descriptors >= 128 */
+			if (desc->fip_dtype < FIP_DT_NON_CRITICAL)
+				return -EINVAL;
+			break;
+		}
+		desc = (struct fip_desc *)((char *)desc + dlen);
+		rlen -= dlen;
+	}
+	if (!fcf->fc_map || (fcf->fc_map & 0x10000))
+		return -EINVAL;
+	if (!fcf->switch_name)
+		return -EINVAL;
+	if (desc_mask) {
+		LIBFCOE_FIP_DBG(fip, "adv missing descriptors mask %x\n",
+				desc_mask);
+		return -EINVAL;
+	}
+	return 0;
+
+len_err:
+	LIBFCOE_FIP_DBG(fip, "FIP length error in descriptor type %x len %zu\n",
+			desc->fip_dtype, dlen);
+	return -EINVAL;
+}
+
+/**
+ * fcoe_ctlr_recv_adv() - Handle an incoming advertisement
+ * @fip: The FCoE controller receiving the advertisement
+ * @skb: The received FIP packet
+ */
+static void fcoe_ctlr_recv_adv(struct fcoe_ctlr *fip, struct sk_buff *skb)
+{
+	struct fcoe_fcf *fcf;
+	struct fcoe_fcf new;
+	unsigned long sol_tov = msecs_to_jiffies(FCOE_CTRL_SOL_TOV);
+	int first = 0;
+	int mtu_valid;
+	int found = 0;
+	int rc = 0;
+
+	if (fcoe_ctlr_parse_adv(fip, skb, &new))
+		return;
+
+	mutex_lock(&fip->ctlr_mutex);
+	first = list_empty(&fip->fcfs);
+	list_for_each_entry(fcf, &fip->fcfs, list) {
+		if (fcf->switch_name == new.switch_name &&
+		    fcf->fabric_name == new.fabric_name &&
+		    fcf->fc_map == new.fc_map &&
+		    ether_addr_equal(fcf->fcf_mac, new.fcf_mac)) {
+			found = 1;
+			break;
+		}
+	}
+	if (!found) {
+		if (fip->fcf_count >= FCOE_CTLR_FCF_LIMIT)
+			goto out;
+
+		fcf = kmalloc(sizeof(*fcf), GFP_ATOMIC);
+		if (!fcf)
+			goto out;
+
+		memcpy(fcf, &new, sizeof(new));
+		fcf->fip = fip;
+		rc = fcoe_sysfs_fcf_add(fcf);
+		if (rc) {
+			printk(KERN_ERR "Failed to allocate sysfs instance "
+			       "for FCF, fab %16.16llx mac %pM\n",
+			       new.fabric_name, new.fcf_mac);
+			kfree(fcf);
+			goto out;
+		}
+	} else {
+		/*
+		 * Update the FCF's keep-alive descriptor flags.
+		 * Other flag changes from new advertisements are
+		 * ignored after a solicited advertisement is
+		 * received and the FCF is selectable (usable).
+		 */
+		fcf->fd_flags = new.fd_flags;
+		if (!fcoe_ctlr_fcf_usable(fcf))
+			fcf->flags = new.flags;
+
+		if (fcf == fip->sel_fcf && !fcf->fd_flags) {
+			fip->ctlr_ka_time -= fcf->fka_period;
+			fip->ctlr_ka_time += new.fka_period;
+			if (time_before(fip->ctlr_ka_time, fip->timer.expires))
+				mod_timer(&fip->timer, fip->ctlr_ka_time);
+		}
+		fcf->fka_period = new.fka_period;
+		memcpy(fcf->fcf_mac, new.fcf_mac, ETH_ALEN);
+	}
+
+	mtu_valid = fcoe_ctlr_mtu_valid(fcf);
+	fcf->time = jiffies;
+	if (!found)
+		LIBFCOE_FIP_DBG(fip, "New FCF fab %16.16llx mac %pM\n",
+				fcf->fabric_name, fcf->fcf_mac);
+
+	/*
+	 * If this advertisement is not solicited and our max receive size
+	 * hasn't been verified, send a solicited advertisement.
+	 */
+	if (!mtu_valid)
+		fcoe_ctlr_solicit(fip, fcf);
+
+	/*
+	 * If its been a while since we did a solicit, and this is
+	 * the first advertisement we've received, do a multicast
+	 * solicitation to gather as many advertisements as we can
+	 * before selection occurs.
+	 */
+	if (first && time_after(jiffies, fip->sol_time + sol_tov))
+		fcoe_ctlr_solicit(fip, NULL);
+
+	/*
+	 * Put this FCF at the head of the list for priority among equals.
+	 * This helps in the case of an NPV switch which insists we use
+	 * the FCF that answers multicast solicitations, not the others that
+	 * are sending periodic multicast advertisements.
+	 */
+	if (mtu_valid)
+		list_move(&fcf->list, &fip->fcfs);
+
+	/*
+	 * If this is the first validated FCF, note the time and
+	 * set a timer to trigger selection.
+	 */
+	if (mtu_valid && !fip->sel_fcf && !fip->sel_time &&
+	    fcoe_ctlr_fcf_usable(fcf)) {
+		fip->sel_time = jiffies +
+			msecs_to_jiffies(FCOE_CTLR_START_DELAY);
+		if (!timer_pending(&fip->timer) ||
+		    time_before(fip->sel_time, fip->timer.expires))
+			mod_timer(&fip->timer, fip->sel_time);
+	}
+
+out:
+	mutex_unlock(&fip->ctlr_mutex);
+}
+
+/**
+ * fcoe_ctlr_recv_els() - Handle an incoming FIP encapsulated ELS frame
+ * @fip: The FCoE controller which received the packet
+ * @skb: The received FIP packet
+ */
+static void fcoe_ctlr_recv_els(struct fcoe_ctlr *fip, struct sk_buff *skb)
+{
+	struct fc_lport *lport = fip->lp;
+	struct fip_header *fiph;
+	struct fc_frame *fp = (struct fc_frame *)skb;
+	struct fc_frame_header *fh = NULL;
+	struct fip_desc *desc;
+	struct fip_encaps *els;
+	struct fcoe_fcf *sel;
+	struct fc_stats *stats;
+	enum fip_desc_type els_dtype = 0;
+	u8 els_op;
+	u8 sub;
+	u8 granted_mac[ETH_ALEN] = { 0 };
+	size_t els_len = 0;
+	size_t rlen;
+	size_t dlen;
+	u32 desc_mask = 0;
+	u32 desc_cnt = 0;
+
+	fiph = (struct fip_header *)skb->data;
+	sub = fiph->fip_subcode;
+	if (sub != FIP_SC_REQ && sub != FIP_SC_REP)
+		goto drop;
+
+	rlen = ntohs(fiph->fip_dl_len) * 4;
+	if (rlen + sizeof(*fiph) > skb->len)
+		goto drop;
+
+	desc = (struct fip_desc *)(fiph + 1);
+	while (rlen > 0) {
+		desc_cnt++;
+		dlen = desc->fip_dlen * FIP_BPW;
+		if (dlen < sizeof(*desc) || dlen > rlen)
+			goto drop;
+		/* Drop ELS if there are duplicate critical descriptors */
+		if (desc->fip_dtype < 32) {
+			if ((desc->fip_dtype != FIP_DT_MAC) &&
+			    (desc_mask & 1U << desc->fip_dtype)) {
+				LIBFCOE_FIP_DBG(fip, "Duplicate Critical "
+						"Descriptors in FIP ELS\n");
+				goto drop;
+			}
+			desc_mask |= (1 << desc->fip_dtype);
+		}
+		switch (desc->fip_dtype) {
+		case FIP_DT_MAC:
+			sel = fip->sel_fcf;
+			if (desc_cnt == 1) {
+				LIBFCOE_FIP_DBG(fip, "FIP descriptors "
+						"received out of order\n");
+				goto drop;
+			}
+			/*
+			 * Some switch implementations send two MAC descriptors,
+			 * with first MAC(granted_mac) being the FPMA, and the
+			 * second one(fcoe_mac) is used as destination address
+			 * for sending/receiving FCoE packets. FIP traffic is
+			 * sent using fip_mac. For regular switches, both
+			 * fip_mac and fcoe_mac would be the same.
+			 */
+			if (desc_cnt == 2)
+				memcpy(granted_mac,
+				       ((struct fip_mac_desc *)desc)->fd_mac,
+				       ETH_ALEN);
+
+			if (dlen != sizeof(struct fip_mac_desc))
+				goto len_err;
+
+			if ((desc_cnt == 3) && (sel))
+				memcpy(sel->fcoe_mac,
+				       ((struct fip_mac_desc *)desc)->fd_mac,
+				       ETH_ALEN);
+			break;
+		case FIP_DT_FLOGI:
+		case FIP_DT_FDISC:
+		case FIP_DT_LOGO:
+		case FIP_DT_ELP:
+			if (desc_cnt != 1) {
+				LIBFCOE_FIP_DBG(fip, "FIP descriptors "
+						"received out of order\n");
+				goto drop;
+			}
+			if (fh)
+				goto drop;
+			if (dlen < sizeof(*els) + sizeof(*fh) + 1)
+				goto len_err;
+			els_len = dlen - sizeof(*els);
+			els = (struct fip_encaps *)desc;
+			fh = (struct fc_frame_header *)(els + 1);
+			els_dtype = desc->fip_dtype;
+			break;
+		default:
+			LIBFCOE_FIP_DBG(fip, "unexpected descriptor type %x "
+					"in FIP adv\n", desc->fip_dtype);
+			/* standard says ignore unknown descriptors >= 128 */
+			if (desc->fip_dtype < FIP_DT_NON_CRITICAL)
+				goto drop;
+			if (desc_cnt <= 2) {
+				LIBFCOE_FIP_DBG(fip, "FIP descriptors "
+						"received out of order\n");
+				goto drop;
+			}
+			break;
+		}
+		desc = (struct fip_desc *)((char *)desc + dlen);
+		rlen -= dlen;
+	}
+
+	if (!fh)
+		goto drop;
+	els_op = *(u8 *)(fh + 1);
+
+	if ((els_dtype == FIP_DT_FLOGI || els_dtype == FIP_DT_FDISC) &&
+	    sub == FIP_SC_REP && fip->mode != FIP_MODE_VN2VN) {
+		if (els_op == ELS_LS_ACC) {
+			if (!is_valid_ether_addr(granted_mac)) {
+				LIBFCOE_FIP_DBG(fip,
+					"Invalid MAC address %pM in FIP ELS\n",
+					granted_mac);
+				goto drop;
+			}
+			memcpy(fr_cb(fp)->granted_mac, granted_mac, ETH_ALEN);
+
+			if (fip->flogi_oxid == ntohs(fh->fh_ox_id)) {
+				fip->flogi_oxid = FC_XID_UNKNOWN;
+				if (els_dtype == FIP_DT_FLOGI)
+					fcoe_ctlr_announce(fip);
+			}
+		} else if (els_dtype == FIP_DT_FLOGI &&
+			   !fcoe_ctlr_flogi_retry(fip))
+			goto drop;	/* retrying FLOGI so drop reject */
+	}
+
+	if ((desc_cnt == 0) || ((els_op != ELS_LS_RJT) &&
+	    (!(1U << FIP_DT_MAC & desc_mask)))) {
+		LIBFCOE_FIP_DBG(fip, "Missing critical descriptors "
+				"in FIP ELS\n");
+		goto drop;
+	}
+
+	/*
+	 * Convert skb into an fc_frame containing only the ELS.
+	 */
+	skb_pull(skb, (u8 *)fh - skb->data);
+	skb_trim(skb, els_len);
+	fp = (struct fc_frame *)skb;
+	fc_frame_init(fp);
+	fr_sof(fp) = FC_SOF_I3;
+	fr_eof(fp) = FC_EOF_T;
+	fr_dev(fp) = lport;
+	fr_encaps(fp) = els_dtype;
+
+	stats = per_cpu_ptr(lport->stats, get_cpu());
+	stats->RxFrames++;
+	stats->RxWords += skb->len / FIP_BPW;
+	put_cpu();
+
+	fc_exch_recv(lport, fp);
+	return;
+
+len_err:
+	LIBFCOE_FIP_DBG(fip, "FIP length error in descriptor type %x len %zu\n",
+			desc->fip_dtype, dlen);
+drop:
+	kfree_skb(skb);
+}
+
+/**
+ * fcoe_ctlr_recv_els() - Handle an incoming link reset frame
+ * @fip: The FCoE controller that received the frame
+ * @fh:	 The received FIP header
+ *
+ * There may be multiple VN_Port descriptors.
+ * The overall length has already been checked.
+ */
+static void fcoe_ctlr_recv_clr_vlink(struct fcoe_ctlr *fip,
+				     struct sk_buff *skb)
+{
+	struct fip_desc *desc;
+	struct fip_mac_desc *mp;
+	struct fip_wwn_desc *wp;
+	struct fip_vn_desc *vp;
+	size_t rlen;
+	size_t dlen;
+	struct fcoe_fcf *fcf = fip->sel_fcf;
+	struct fc_lport *lport = fip->lp;
+	struct fc_lport *vn_port = NULL;
+	u32 desc_mask;
+	int num_vlink_desc;
+	int reset_phys_port = 0;
+	struct fip_vn_desc **vlink_desc_arr = NULL;
+	struct fip_header *fh = (struct fip_header *)skb->data;
+	struct ethhdr *eh = eth_hdr(skb);
+
+	LIBFCOE_FIP_DBG(fip, "Clear Virtual Link received\n");
+
+	if (!fcf) {
+		/*
+		 * We are yet to select best FCF, but we got CVL in the
+		 * meantime. reset the ctlr and let it rediscover the FCF
+		 */
+		LIBFCOE_FIP_DBG(fip, "Resetting fcoe_ctlr as FCF has not been "
+		    "selected yet\n");
+		mutex_lock(&fip->ctlr_mutex);
+		fcoe_ctlr_reset(fip);
+		mutex_unlock(&fip->ctlr_mutex);
+		return;
+	}
+
+	/*
+	 * If we've selected an FCF check that the CVL is from there to avoid
+	 * processing CVLs from an unexpected source.  If it is from an
+	 * unexpected source drop it on the floor.
+	 */
+	if (!ether_addr_equal(eh->h_source, fcf->fcf_mac)) {
+		LIBFCOE_FIP_DBG(fip, "Dropping CVL due to source address "
+		    "mismatch with FCF src=%pM\n", eh->h_source);
+		return;
+	}
+
+	/*
+	 * If we haven't logged into the fabric but receive a CVL we should
+	 * reset everything and go back to solicitation.
+	 */
+	if (!lport->port_id) {
+		LIBFCOE_FIP_DBG(fip, "lport not logged in, resoliciting\n");
+		mutex_lock(&fip->ctlr_mutex);
+		fcoe_ctlr_reset(fip);
+		mutex_unlock(&fip->ctlr_mutex);
+		fc_lport_reset(fip->lp);
+		fcoe_ctlr_solicit(fip, NULL);
+		return;
+	}
+
+	/*
+	 * mask of required descriptors.  Validating each one clears its bit.
+	 */
+	desc_mask = BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME);
+
+	rlen = ntohs(fh->fip_dl_len) * FIP_BPW;
+	desc = (struct fip_desc *)(fh + 1);
+
+	/*
+	 * Actually need to subtract 'sizeof(*mp) - sizeof(*wp)' from 'rlen'
+	 * before determining max Vx_Port descriptor but a buggy FCF could have
+	 * omitted either or both MAC Address and Name Identifier descriptors
+	 */
+	num_vlink_desc = rlen / sizeof(*vp);
+	if (num_vlink_desc)
+		vlink_desc_arr = kmalloc_array(num_vlink_desc, sizeof(vp),
+					       GFP_ATOMIC);
+	if (!vlink_desc_arr)
+		return;
+	num_vlink_desc = 0;
+
+	while (rlen >= sizeof(*desc)) {
+		dlen = desc->fip_dlen * FIP_BPW;
+		if (dlen > rlen)
+			goto err;
+		/* Drop CVL if there are duplicate critical descriptors */
+		if ((desc->fip_dtype < 32) &&
+		    (desc->fip_dtype != FIP_DT_VN_ID) &&
+		    !(desc_mask & 1U << desc->fip_dtype)) {
+			LIBFCOE_FIP_DBG(fip, "Duplicate Critical "
+					"Descriptors in FIP CVL\n");
+			goto err;
+		}
+		switch (desc->fip_dtype) {
+		case FIP_DT_MAC:
+			mp = (struct fip_mac_desc *)desc;
+			if (dlen < sizeof(*mp))
+				goto err;
+			if (!ether_addr_equal(mp->fd_mac, fcf->fcf_mac))
+				goto err;
+			desc_mask &= ~BIT(FIP_DT_MAC);
+			break;
+		case FIP_DT_NAME:
+			wp = (struct fip_wwn_desc *)desc;
+			if (dlen < sizeof(*wp))
+				goto err;
+			if (get_unaligned_be64(&wp->fd_wwn) != fcf->switch_name)
+				goto err;
+			desc_mask &= ~BIT(FIP_DT_NAME);
+			break;
+		case FIP_DT_VN_ID:
+			vp = (struct fip_vn_desc *)desc;
+			if (dlen < sizeof(*vp))
+				goto err;
+			vlink_desc_arr[num_vlink_desc++] = vp;
+			vn_port = fc_vport_id_lookup(lport,
+						      ntoh24(vp->fd_fc_id));
+			if (vn_port && (vn_port == lport)) {
+				mutex_lock(&fip->ctlr_mutex);
+				per_cpu_ptr(lport->stats,
+					    get_cpu())->VLinkFailureCount++;
+				put_cpu();
+				fcoe_ctlr_reset(fip);
+				mutex_unlock(&fip->ctlr_mutex);
+			}
+			break;
+		default:
+			/* standard says ignore unknown descriptors >= 128 */
+			if (desc->fip_dtype < FIP_DT_NON_CRITICAL)
+				goto err;
+			break;
+		}
+		desc = (struct fip_desc *)((char *)desc + dlen);
+		rlen -= dlen;
+	}
+
+	/*
+	 * reset only if all required descriptors were present and valid.
+	 */
+	if (desc_mask)
+		LIBFCOE_FIP_DBG(fip, "missing descriptors mask %x\n",
+				desc_mask);
+	else if (!num_vlink_desc) {
+		LIBFCOE_FIP_DBG(fip, "CVL: no Vx_Port descriptor found\n");
+		/*
+		 * No Vx_Port description. Clear all NPIV ports,
+		 * followed by physical port
+		 */
+		mutex_lock(&fip->ctlr_mutex);
+		per_cpu_ptr(lport->stats, get_cpu())->VLinkFailureCount++;
+		put_cpu();
+		fcoe_ctlr_reset(fip);
+		mutex_unlock(&fip->ctlr_mutex);
+
+		mutex_lock(&lport->lp_mutex);
+		list_for_each_entry(vn_port, &lport->vports, list)
+			fc_lport_reset(vn_port);
+		mutex_unlock(&lport->lp_mutex);
+
+		fc_lport_reset(fip->lp);
+		fcoe_ctlr_solicit(fip, NULL);
+	} else {
+		int i;
+
+		LIBFCOE_FIP_DBG(fip, "performing Clear Virtual Link\n");
+		for (i = 0; i < num_vlink_desc; i++) {
+			vp = vlink_desc_arr[i];
+			vn_port = fc_vport_id_lookup(lport,
+						     ntoh24(vp->fd_fc_id));
+			if (!vn_port)
+				continue;
+
+			/*
+			 * 'port_id' is already validated, check MAC address and
+			 * wwpn
+			 */
+			if (!ether_addr_equal(fip->get_src_addr(vn_port),
+					      vp->fd_mac) ||
+				get_unaligned_be64(&vp->fd_wwpn) !=
+							vn_port->wwpn)
+				continue;
+
+			if (vn_port == lport)
+				/*
+				 * Physical port, defer processing till all
+				 * listed NPIV ports are cleared
+				 */
+				reset_phys_port = 1;
+			else    /* NPIV port */
+				fc_lport_reset(vn_port);
+		}
+
+		if (reset_phys_port) {
+			fc_lport_reset(fip->lp);
+			fcoe_ctlr_solicit(fip, NULL);
+		}
+	}
+
+err:
+	kfree(vlink_desc_arr);
+}
+
+/**
+ * fcoe_ctlr_recv() - Receive a FIP packet
+ * @fip: The FCoE controller that received the packet
+ * @skb: The received FIP packet
+ *
+ * This may be called from either NET_RX_SOFTIRQ or IRQ.
+ */
+void fcoe_ctlr_recv(struct fcoe_ctlr *fip, struct sk_buff *skb)
+{
+	skb = skb_share_check(skb, GFP_ATOMIC);
+	if (!skb)
+		return;
+	skb_queue_tail(&fip->fip_recv_list, skb);
+	schedule_work(&fip->recv_work);
+}
+EXPORT_SYMBOL(fcoe_ctlr_recv);
+
+/**
+ * fcoe_ctlr_recv_handler() - Receive a FIP frame
+ * @fip: The FCoE controller that received the frame
+ * @skb: The received FIP frame
+ *
+ * Returns non-zero if the frame is dropped.
+ */
+static int fcoe_ctlr_recv_handler(struct fcoe_ctlr *fip, struct sk_buff *skb)
+{
+	struct fip_header *fiph;
+	struct ethhdr *eh;
+	enum fip_state state;
+	bool fip_vlan_resp = false;
+	u16 op;
+	u8 sub;
+
+	if (skb_linearize(skb))
+		goto drop;
+	if (skb->len < sizeof(*fiph))
+		goto drop;
+	eh = eth_hdr(skb);
+	if (fip->mode == FIP_MODE_VN2VN) {
+		if (!ether_addr_equal(eh->h_dest, fip->ctl_src_addr) &&
+		    !ether_addr_equal(eh->h_dest, fcoe_all_vn2vn) &&
+		    !ether_addr_equal(eh->h_dest, fcoe_all_p2p))
+			goto drop;
+	} else if (!ether_addr_equal(eh->h_dest, fip->ctl_src_addr) &&
+		   !ether_addr_equal(eh->h_dest, fcoe_all_enode))
+		goto drop;
+	fiph = (struct fip_header *)skb->data;
+	op = ntohs(fiph->fip_op);
+	sub = fiph->fip_subcode;
+
+	if (FIP_VER_DECAPS(fiph->fip_ver) != FIP_VER)
+		goto drop;
+	if (ntohs(fiph->fip_dl_len) * FIP_BPW + sizeof(*fiph) > skb->len)
+		goto drop;
+
+	mutex_lock(&fip->ctlr_mutex);
+	state = fip->state;
+	if (state == FIP_ST_AUTO) {
+		fip->map_dest = 0;
+		fcoe_ctlr_set_state(fip, FIP_ST_ENABLED);
+		state = FIP_ST_ENABLED;
+		LIBFCOE_FIP_DBG(fip, "Using FIP mode\n");
+	}
+	fip_vlan_resp = fip->fip_resp;
+	mutex_unlock(&fip->ctlr_mutex);
+
+	if (fip->mode == FIP_MODE_VN2VN && op == FIP_OP_VN2VN)
+		return fcoe_ctlr_vn_recv(fip, skb);
+
+	if (fip_vlan_resp && op == FIP_OP_VLAN) {
+		LIBFCOE_FIP_DBG(fip, "fip vlan discovery\n");
+		return fcoe_ctlr_vlan_recv(fip, skb);
+	}
+
+	if (state != FIP_ST_ENABLED && state != FIP_ST_VNMP_UP &&
+	    state != FIP_ST_VNMP_CLAIM)
+		goto drop;
+
+	if (op == FIP_OP_LS) {
+		fcoe_ctlr_recv_els(fip, skb);	/* consumes skb */
+		return 0;
+	}
+
+	if (state != FIP_ST_ENABLED)
+		goto drop;
+
+	if (op == FIP_OP_DISC && sub == FIP_SC_ADV)
+		fcoe_ctlr_recv_adv(fip, skb);
+	else if (op == FIP_OP_CTRL && sub == FIP_SC_CLR_VLINK)
+		fcoe_ctlr_recv_clr_vlink(fip, skb);
+	kfree_skb(skb);
+	return 0;
+drop:
+	kfree_skb(skb);
+	return -1;
+}
+
+/**
+ * fcoe_ctlr_select() - Select the best FCF (if possible)
+ * @fip: The FCoE controller
+ *
+ * Returns the selected FCF, or NULL if none are usable.
+ *
+ * If there are conflicting advertisements, no FCF can be chosen.
+ *
+ * If there is already a selected FCF, this will choose a better one or
+ * an equivalent one that hasn't already been sent a FLOGI.
+ *
+ * Called with lock held.
+ */
+static struct fcoe_fcf *fcoe_ctlr_select(struct fcoe_ctlr *fip)
+{
+	struct fcoe_fcf *fcf;
+	struct fcoe_fcf *best = fip->sel_fcf;
+
+	list_for_each_entry(fcf, &fip->fcfs, list) {
+		LIBFCOE_FIP_DBG(fip, "consider FCF fab %16.16llx "
+				"VFID %d mac %pM map %x val %d "
+				"sent %u pri %u\n",
+				fcf->fabric_name, fcf->vfid, fcf->fcf_mac,
+				fcf->fc_map, fcoe_ctlr_mtu_valid(fcf),
+				fcf->flogi_sent, fcf->pri);
+		if (!fcoe_ctlr_fcf_usable(fcf)) {
+			LIBFCOE_FIP_DBG(fip, "FCF for fab %16.16llx "
+					"map %x %svalid %savailable\n",
+					fcf->fabric_name, fcf->fc_map,
+					(fcf->flags & FIP_FL_SOL) ? "" : "in",
+					(fcf->flags & FIP_FL_AVAIL) ?
+					"" : "un");
+			continue;
+		}
+		if (!best || fcf->pri < best->pri || best->flogi_sent)
+			best = fcf;
+		if (fcf->fabric_name != best->fabric_name ||
+		    fcf->vfid != best->vfid ||
+		    fcf->fc_map != best->fc_map) {
+			LIBFCOE_FIP_DBG(fip, "Conflicting fabric, VFID, "
+					"or FC-MAP\n");
+			return NULL;
+		}
+	}
+	fip->sel_fcf = best;
+	if (best) {
+		LIBFCOE_FIP_DBG(fip, "using FCF mac %pM\n", best->fcf_mac);
+		fip->port_ka_time = jiffies +
+			msecs_to_jiffies(FIP_VN_KA_PERIOD);
+		fip->ctlr_ka_time = jiffies + best->fka_period;
+		if (time_before(fip->ctlr_ka_time, fip->timer.expires))
+			mod_timer(&fip->timer, fip->ctlr_ka_time);
+	}
+	return best;
+}
+
+/**
+ * fcoe_ctlr_flogi_send_locked() - send FIP-encapsulated FLOGI to current FCF
+ * @fip: The FCoE controller
+ *
+ * Returns non-zero error if it could not be sent.
+ *
+ * Called with ctlr_mutex and ctlr_lock held.
+ * Caller must verify that fip->sel_fcf is not NULL.
+ */
+static int fcoe_ctlr_flogi_send_locked(struct fcoe_ctlr *fip)
+{
+	struct sk_buff *skb;
+	struct sk_buff *skb_orig;
+	struct fc_frame_header *fh;
+	int error;
+
+	skb_orig = fip->flogi_req;
+	if (!skb_orig)
+		return -EINVAL;
+
+	/*
+	 * Clone and send the FLOGI request.  If clone fails, use original.
+	 */
+	skb = skb_clone(skb_orig, GFP_ATOMIC);
+	if (!skb) {
+		skb = skb_orig;
+		fip->flogi_req = NULL;
+	}
+	fh = (struct fc_frame_header *)skb->data;
+	error = fcoe_ctlr_encaps(fip, fip->lp, FIP_DT_FLOGI, skb,
+				 ntoh24(fh->fh_d_id));
+	if (error) {
+		kfree_skb(skb);
+		return error;
+	}
+	fip->send(fip, skb);
+	fip->sel_fcf->flogi_sent = 1;
+	return 0;
+}
+
+/**
+ * fcoe_ctlr_flogi_retry() - resend FLOGI request to a new FCF if possible
+ * @fip: The FCoE controller
+ *
+ * Returns non-zero error code if there's no FLOGI request to retry or
+ * no alternate FCF available.
+ */
+static int fcoe_ctlr_flogi_retry(struct fcoe_ctlr *fip)
+{
+	struct fcoe_fcf *fcf;
+	int error;
+
+	mutex_lock(&fip->ctlr_mutex);
+	spin_lock_bh(&fip->ctlr_lock);
+	LIBFCOE_FIP_DBG(fip, "re-sending FLOGI - reselect\n");
+	fcf = fcoe_ctlr_select(fip);
+	if (!fcf || fcf->flogi_sent) {
+		kfree_skb(fip->flogi_req);
+		fip->flogi_req = NULL;
+		error = -ENOENT;
+	} else {
+		fcoe_ctlr_solicit(fip, NULL);
+		error = fcoe_ctlr_flogi_send_locked(fip);
+	}
+	spin_unlock_bh(&fip->ctlr_lock);
+	mutex_unlock(&fip->ctlr_mutex);
+	return error;
+}
+
+
+/**
+ * fcoe_ctlr_flogi_send() - Handle sending of FIP FLOGI.
+ * @fip: The FCoE controller that timed out
+ *
+ * Done here because fcoe_ctlr_els_send() can't get mutex.
+ *
+ * Called with ctlr_mutex held.  The caller must not hold ctlr_lock.
+ */
+static void fcoe_ctlr_flogi_send(struct fcoe_ctlr *fip)
+{
+	struct fcoe_fcf *fcf;
+
+	spin_lock_bh(&fip->ctlr_lock);
+	fcf = fip->sel_fcf;
+	if (!fcf || !fip->flogi_req_send)
+		goto unlock;
+
+	LIBFCOE_FIP_DBG(fip, "sending FLOGI\n");
+
+	/*
+	 * If this FLOGI is being sent due to a timeout retry
+	 * to the same FCF as before, select a different FCF if possible.
+	 */
+	if (fcf->flogi_sent) {
+		LIBFCOE_FIP_DBG(fip, "sending FLOGI - reselect\n");
+		fcf = fcoe_ctlr_select(fip);
+		if (!fcf || fcf->flogi_sent) {
+			LIBFCOE_FIP_DBG(fip, "sending FLOGI - clearing\n");
+			list_for_each_entry(fcf, &fip->fcfs, list)
+				fcf->flogi_sent = 0;
+			fcf = fcoe_ctlr_select(fip);
+		}
+	}
+	if (fcf) {
+		fcoe_ctlr_flogi_send_locked(fip);
+		fip->flogi_req_send = 0;
+	} else /* XXX */
+		LIBFCOE_FIP_DBG(fip, "No FCF selected - defer send\n");
+unlock:
+	spin_unlock_bh(&fip->ctlr_lock);
+}
+
+/**
+ * fcoe_ctlr_timeout() - FIP timeout handler
+ * @arg: The FCoE controller that timed out
+ */
+static void fcoe_ctlr_timeout(struct timer_list *t)
+{
+	struct fcoe_ctlr *fip = from_timer(fip, t, timer);
+
+	schedule_work(&fip->timer_work);
+}
+
+/**
+ * fcoe_ctlr_timer_work() - Worker thread function for timer work
+ * @work: Handle to a FCoE controller
+ *
+ * Ages FCFs.  Triggers FCF selection if possible.
+ * Sends keep-alives and resets.
+ */
+static void fcoe_ctlr_timer_work(struct work_struct *work)
+{
+	struct fcoe_ctlr *fip;
+	struct fc_lport *vport;
+	u8 *mac;
+	u8 reset = 0;
+	u8 send_ctlr_ka = 0;
+	u8 send_port_ka = 0;
+	struct fcoe_fcf *sel;
+	struct fcoe_fcf *fcf;
+	unsigned long next_timer;
+
+	fip = container_of(work, struct fcoe_ctlr, timer_work);
+	if (fip->mode == FIP_MODE_VN2VN)
+		return fcoe_ctlr_vn_timeout(fip);
+	mutex_lock(&fip->ctlr_mutex);
+	if (fip->state == FIP_ST_DISABLED) {
+		mutex_unlock(&fip->ctlr_mutex);
+		return;
+	}
+
+	fcf = fip->sel_fcf;
+	next_timer = fcoe_ctlr_age_fcfs(fip);
+
+	sel = fip->sel_fcf;
+	if (!sel && fip->sel_time) {
+		if (time_after_eq(jiffies, fip->sel_time)) {
+			sel = fcoe_ctlr_select(fip);
+			fip->sel_time = 0;
+		} else if (time_after(next_timer, fip->sel_time))
+			next_timer = fip->sel_time;
+	}
+
+	if (sel && fip->flogi_req_send)
+		fcoe_ctlr_flogi_send(fip);
+	else if (!sel && fcf)
+		reset = 1;
+
+	if (sel && !sel->fd_flags) {
+		if (time_after_eq(jiffies, fip->ctlr_ka_time)) {
+			fip->ctlr_ka_time = jiffies + sel->fka_period;
+			send_ctlr_ka = 1;
+		}
+		if (time_after(next_timer, fip->ctlr_ka_time))
+			next_timer = fip->ctlr_ka_time;
+
+		if (time_after_eq(jiffies, fip->port_ka_time)) {
+			fip->port_ka_time = jiffies +
+				msecs_to_jiffies(FIP_VN_KA_PERIOD);
+			send_port_ka = 1;
+		}
+		if (time_after(next_timer, fip->port_ka_time))
+			next_timer = fip->port_ka_time;
+	}
+	if (!list_empty(&fip->fcfs))
+		mod_timer(&fip->timer, next_timer);
+	mutex_unlock(&fip->ctlr_mutex);
+
+	if (reset) {
+		fc_lport_reset(fip->lp);
+		/* restart things with a solicitation */
+		fcoe_ctlr_solicit(fip, NULL);
+	}
+
+	if (send_ctlr_ka)
+		fcoe_ctlr_send_keep_alive(fip, NULL, 0, fip->ctl_src_addr);
+
+	if (send_port_ka) {
+		mutex_lock(&fip->lp->lp_mutex);
+		mac = fip->get_src_addr(fip->lp);
+		fcoe_ctlr_send_keep_alive(fip, fip->lp, 1, mac);
+		list_for_each_entry(vport, &fip->lp->vports, list) {
+			mac = fip->get_src_addr(vport);
+			fcoe_ctlr_send_keep_alive(fip, vport, 1, mac);
+		}
+		mutex_unlock(&fip->lp->lp_mutex);
+	}
+}
+
+/**
+ * fcoe_ctlr_recv_work() - Worker thread function for receiving FIP frames
+ * @recv_work: Handle to a FCoE controller
+ */
+static void fcoe_ctlr_recv_work(struct work_struct *recv_work)
+{
+	struct fcoe_ctlr *fip;
+	struct sk_buff *skb;
+
+	fip = container_of(recv_work, struct fcoe_ctlr, recv_work);
+	while ((skb = skb_dequeue(&fip->fip_recv_list)))
+		fcoe_ctlr_recv_handler(fip, skb);
+}
+
+/**
+ * fcoe_ctlr_recv_flogi() - Snoop pre-FIP receipt of FLOGI response
+ * @fip: The FCoE controller
+ * @fp:	 The FC frame to snoop
+ *
+ * Snoop potential response to FLOGI or even incoming FLOGI.
+ *
+ * The caller has checked that we are waiting for login as indicated
+ * by fip->flogi_oxid != FC_XID_UNKNOWN.
+ *
+ * The caller is responsible for freeing the frame.
+ * Fill in the granted_mac address.
+ *
+ * Return non-zero if the frame should not be delivered to libfc.
+ */
+int fcoe_ctlr_recv_flogi(struct fcoe_ctlr *fip, struct fc_lport *lport,
+			 struct fc_frame *fp)
+{
+	struct fc_frame_header *fh;
+	u8 op;
+	u8 *sa;
+
+	sa = eth_hdr(&fp->skb)->h_source;
+	fh = fc_frame_header_get(fp);
+	if (fh->fh_type != FC_TYPE_ELS)
+		return 0;
+
+	op = fc_frame_payload_op(fp);
+	if (op == ELS_LS_ACC && fh->fh_r_ctl == FC_RCTL_ELS_REP &&
+	    fip->flogi_oxid == ntohs(fh->fh_ox_id)) {
+
+		mutex_lock(&fip->ctlr_mutex);
+		if (fip->state != FIP_ST_AUTO && fip->state != FIP_ST_NON_FIP) {
+			mutex_unlock(&fip->ctlr_mutex);
+			return -EINVAL;
+		}
+		fcoe_ctlr_set_state(fip, FIP_ST_NON_FIP);
+		LIBFCOE_FIP_DBG(fip,
+				"received FLOGI LS_ACC using non-FIP mode\n");
+
+		/*
+		 * FLOGI accepted.
+		 * If the src mac addr is FC_OUI-based, then we mark the
+		 * address_mode flag to use FC_OUI-based Ethernet DA.
+		 * Otherwise we use the FCoE gateway addr
+		 */
+		if (ether_addr_equal(sa, (u8[6])FC_FCOE_FLOGI_MAC)) {
+			fcoe_ctlr_map_dest(fip);
+		} else {
+			memcpy(fip->dest_addr, sa, ETH_ALEN);
+			fip->map_dest = 0;
+		}
+		fip->flogi_oxid = FC_XID_UNKNOWN;
+		mutex_unlock(&fip->ctlr_mutex);
+		fc_fcoe_set_mac(fr_cb(fp)->granted_mac, fh->fh_d_id);
+	} else if (op == ELS_FLOGI && fh->fh_r_ctl == FC_RCTL_ELS_REQ && sa) {
+		/*
+		 * Save source MAC for point-to-point responses.
+		 */
+		mutex_lock(&fip->ctlr_mutex);
+		if (fip->state == FIP_ST_AUTO || fip->state == FIP_ST_NON_FIP) {
+			memcpy(fip->dest_addr, sa, ETH_ALEN);
+			fip->map_dest = 0;
+			if (fip->state == FIP_ST_AUTO)
+				LIBFCOE_FIP_DBG(fip, "received non-FIP FLOGI. "
+						"Setting non-FIP mode\n");
+			fcoe_ctlr_set_state(fip, FIP_ST_NON_FIP);
+		}
+		mutex_unlock(&fip->ctlr_mutex);
+	}
+	return 0;
+}
+EXPORT_SYMBOL(fcoe_ctlr_recv_flogi);
+
+/**
+ * fcoe_wwn_from_mac() - Converts a 48-bit IEEE MAC address to a 64-bit FC WWN
+ * @mac:    The MAC address to convert
+ * @scheme: The scheme to use when converting
+ * @port:   The port indicator for converting
+ *
+ * Returns: u64 fc world wide name
+ */
+u64 fcoe_wwn_from_mac(unsigned char mac[MAX_ADDR_LEN],
+		      unsigned int scheme, unsigned int port)
+{
+	u64 wwn;
+	u64 host_mac;
+
+	/* The MAC is in NO, so flip only the low 48 bits */
+	host_mac = ((u64) mac[0] << 40) |
+		((u64) mac[1] << 32) |
+		((u64) mac[2] << 24) |
+		((u64) mac[3] << 16) |
+		((u64) mac[4] << 8) |
+		(u64) mac[5];
+
+	WARN_ON(host_mac >= (1ULL << 48));
+	wwn = host_mac | ((u64) scheme << 60);
+	switch (scheme) {
+	case 1:
+		WARN_ON(port != 0);
+		break;
+	case 2:
+		WARN_ON(port >= 0xfff);
+		wwn |= (u64) port << 48;
+		break;
+	default:
+		WARN_ON(1);
+		break;
+	}
+
+	return wwn;
+}
+EXPORT_SYMBOL_GPL(fcoe_wwn_from_mac);
+
+/**
+ * fcoe_ctlr_rport() - return the fcoe_rport for a given fc_rport_priv
+ * @rdata: libfc remote port
+ */
+static inline struct fcoe_rport *fcoe_ctlr_rport(struct fc_rport_priv *rdata)
+{
+	return (struct fcoe_rport *)(rdata + 1);
+}
+
+/**
+ * fcoe_ctlr_vn_send() - Send a FIP VN2VN Probe Request or Reply.
+ * @fip: The FCoE controller
+ * @sub: sub-opcode for probe request, reply, or advertisement.
+ * @dest: The destination Ethernet MAC address
+ * @min_len: minimum size of the Ethernet payload to be sent
+ */
+static void fcoe_ctlr_vn_send(struct fcoe_ctlr *fip,
+			      enum fip_vn2vn_subcode sub,
+			      const u8 *dest, size_t min_len)
+{
+	struct sk_buff *skb;
+	struct fip_vn2vn_probe_frame {
+		struct ethhdr eth;
+		struct fip_header fip;
+		struct fip_mac_desc mac;
+		struct fip_wwn_desc wwnn;
+		struct fip_vn_desc vn;
+	} __packed * frame;
+	struct fip_fc4_feat *ff;
+	struct fip_size_desc *size;
+	u32 fcp_feat;
+	size_t len;
+	size_t dlen;
+
+	len = sizeof(*frame);
+	dlen = 0;
+	if (sub == FIP_SC_VN_CLAIM_NOTIFY || sub == FIP_SC_VN_CLAIM_REP) {
+		dlen = sizeof(struct fip_fc4_feat) +
+		       sizeof(struct fip_size_desc);
+		len += dlen;
+	}
+	dlen += sizeof(frame->mac) + sizeof(frame->wwnn) + sizeof(frame->vn);
+	len = max(len, min_len + sizeof(struct ethhdr));
+
+	skb = dev_alloc_skb(len);
+	if (!skb)
+		return;
+
+	frame = (struct fip_vn2vn_probe_frame *)skb->data;
+	memset(frame, 0, len);
+	memcpy(frame->eth.h_dest, dest, ETH_ALEN);
+
+	if (sub == FIP_SC_VN_BEACON) {
+		hton24(frame->eth.h_source, FIP_VN_FC_MAP);
+		hton24(frame->eth.h_source + 3, fip->port_id);
+	} else {
+		memcpy(frame->eth.h_source, fip->ctl_src_addr, ETH_ALEN);
+	}
+	frame->eth.h_proto = htons(ETH_P_FIP);
+
+	frame->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER);
+	frame->fip.fip_op = htons(FIP_OP_VN2VN);
+	frame->fip.fip_subcode = sub;
+	frame->fip.fip_dl_len = htons(dlen / FIP_BPW);
+
+	frame->mac.fd_desc.fip_dtype = FIP_DT_MAC;
+	frame->mac.fd_desc.fip_dlen = sizeof(frame->mac) / FIP_BPW;
+	memcpy(frame->mac.fd_mac, fip->ctl_src_addr, ETH_ALEN);
+
+	frame->wwnn.fd_desc.fip_dtype = FIP_DT_NAME;
+	frame->wwnn.fd_desc.fip_dlen = sizeof(frame->wwnn) / FIP_BPW;
+	put_unaligned_be64(fip->lp->wwnn, &frame->wwnn.fd_wwn);
+
+	frame->vn.fd_desc.fip_dtype = FIP_DT_VN_ID;
+	frame->vn.fd_desc.fip_dlen = sizeof(frame->vn) / FIP_BPW;
+	hton24(frame->vn.fd_mac, FIP_VN_FC_MAP);
+	hton24(frame->vn.fd_mac + 3, fip->port_id);
+	hton24(frame->vn.fd_fc_id, fip->port_id);
+	put_unaligned_be64(fip->lp->wwpn, &frame->vn.fd_wwpn);
+
+	/*
+	 * For claims, add FC-4 features.
+	 * TBD: Add interface to get fc-4 types and features from libfc.
+	 */
+	if (sub == FIP_SC_VN_CLAIM_NOTIFY || sub == FIP_SC_VN_CLAIM_REP) {
+		ff = (struct fip_fc4_feat *)(frame + 1);
+		ff->fd_desc.fip_dtype = FIP_DT_FC4F;
+		ff->fd_desc.fip_dlen = sizeof(*ff) / FIP_BPW;
+		ff->fd_fts = fip->lp->fcts;
+
+		fcp_feat = 0;
+		if (fip->lp->service_params & FCP_SPPF_INIT_FCN)
+			fcp_feat |= FCP_FEAT_INIT;
+		if (fip->lp->service_params & FCP_SPPF_TARG_FCN)
+			fcp_feat |= FCP_FEAT_TARG;
+		fcp_feat <<= (FC_TYPE_FCP * 4) % 32;
+		ff->fd_ff.fd_feat[FC_TYPE_FCP * 4 / 32] = htonl(fcp_feat);
+
+		size = (struct fip_size_desc *)(ff + 1);
+		size->fd_desc.fip_dtype = FIP_DT_FCOE_SIZE;
+		size->fd_desc.fip_dlen = sizeof(*size) / FIP_BPW;
+		size->fd_size = htons(fcoe_ctlr_fcoe_size(fip));
+	}
+
+	skb_put(skb, len);
+	skb->protocol = htons(ETH_P_FIP);
+	skb->priority = fip->priority;
+	skb_reset_mac_header(skb);
+	skb_reset_network_header(skb);
+
+	fip->send(fip, skb);
+}
+
+/**
+ * fcoe_ctlr_vn_rport_callback - Event handler for rport events.
+ * @lport: The lport which is receiving the event
+ * @rdata: remote port private data
+ * @event: The event that occurred
+ *
+ * Locking Note:  The rport lock must not be held when calling this function.
+ */
+static void fcoe_ctlr_vn_rport_callback(struct fc_lport *lport,
+					struct fc_rport_priv *rdata,
+					enum fc_rport_event event)
+{
+	struct fcoe_ctlr *fip = lport->disc.priv;
+	struct fcoe_rport *frport = fcoe_ctlr_rport(rdata);
+
+	LIBFCOE_FIP_DBG(fip, "vn_rport_callback %x event %d\n",
+			rdata->ids.port_id, event);
+
+	mutex_lock(&fip->ctlr_mutex);
+	switch (event) {
+	case RPORT_EV_READY:
+		frport->login_count = 0;
+		break;
+	case RPORT_EV_LOGO:
+	case RPORT_EV_FAILED:
+	case RPORT_EV_STOP:
+		frport->login_count++;
+		if (frport->login_count > FCOE_CTLR_VN2VN_LOGIN_LIMIT) {
+			LIBFCOE_FIP_DBG(fip,
+					"rport FLOGI limited port_id %6.6x\n",
+					rdata->ids.port_id);
+			fc_rport_logoff(rdata);
+		}
+		break;
+	default:
+		break;
+	}
+	mutex_unlock(&fip->ctlr_mutex);
+}
+
+static struct fc_rport_operations fcoe_ctlr_vn_rport_ops = {
+	.event_callback = fcoe_ctlr_vn_rport_callback,
+};
+
+/**
+ * fcoe_ctlr_disc_stop_locked() - stop discovery in VN2VN mode
+ * @fip: The FCoE controller
+ *
+ * Called with ctlr_mutex held.
+ */
+static void fcoe_ctlr_disc_stop_locked(struct fc_lport *lport)
+{
+	struct fc_rport_priv *rdata;
+
+	mutex_lock(&lport->disc.disc_mutex);
+	list_for_each_entry_rcu(rdata, &lport->disc.rports, peers) {
+		if (kref_get_unless_zero(&rdata->kref)) {
+			fc_rport_logoff(rdata);
+			kref_put(&rdata->kref, fc_rport_destroy);
+		}
+	}
+	lport->disc.disc_callback = NULL;
+	mutex_unlock(&lport->disc.disc_mutex);
+}
+
+/**
+ * fcoe_ctlr_disc_stop() - stop discovery in VN2VN mode
+ * @fip: The FCoE controller
+ *
+ * Called through the local port template for discovery.
+ * Called without the ctlr_mutex held.
+ */
+static void fcoe_ctlr_disc_stop(struct fc_lport *lport)
+{
+	struct fcoe_ctlr *fip = lport->disc.priv;
+
+	mutex_lock(&fip->ctlr_mutex);
+	fcoe_ctlr_disc_stop_locked(lport);
+	mutex_unlock(&fip->ctlr_mutex);
+}
+
+/**
+ * fcoe_ctlr_disc_stop_final() - stop discovery for shutdown in VN2VN mode
+ * @fip: The FCoE controller
+ *
+ * Called through the local port template for discovery.
+ * Called without the ctlr_mutex held.
+ */
+static void fcoe_ctlr_disc_stop_final(struct fc_lport *lport)
+{
+	fcoe_ctlr_disc_stop(lport);
+	fc_rport_flush_queue();
+	synchronize_rcu();
+}
+
+/**
+ * fcoe_ctlr_vn_restart() - VN2VN probe restart with new port_id
+ * @fip: The FCoE controller
+ *
+ * Called with fcoe_ctlr lock held.
+ */
+static void fcoe_ctlr_vn_restart(struct fcoe_ctlr *fip)
+{
+	unsigned long wait;
+	u32 port_id;
+
+	fcoe_ctlr_disc_stop_locked(fip->lp);
+
+	/*
+	 * Get proposed port ID.
+	 * If this is the first try after link up, use any previous port_id.
+	 * If there was none, use the low bits of the port_name.
+	 * On subsequent tries, get the next random one.
+	 * Don't use reserved IDs, use another non-zero value, just as random.
+	 */
+	port_id = fip->port_id;
+	if (fip->probe_tries)
+		port_id = prandom_u32_state(&fip->rnd_state) & 0xffff;
+	else if (!port_id)
+		port_id = fip->lp->wwpn & 0xffff;
+	if (!port_id || port_id == 0xffff)
+		port_id = 1;
+	fip->port_id = port_id;
+
+	if (fip->probe_tries < FIP_VN_RLIM_COUNT) {
+		fip->probe_tries++;
+		wait = prandom_u32() % FIP_VN_PROBE_WAIT;
+	} else
+		wait = FIP_VN_RLIM_INT;
+	mod_timer(&fip->timer, jiffies + msecs_to_jiffies(wait));
+	fcoe_ctlr_set_state(fip, FIP_ST_VNMP_START);
+}
+
+/**
+ * fcoe_ctlr_vn_start() - Start in VN2VN mode
+ * @fip: The FCoE controller
+ *
+ * Called with fcoe_ctlr lock held.
+ */
+static void fcoe_ctlr_vn_start(struct fcoe_ctlr *fip)
+{
+	fip->probe_tries = 0;
+	prandom_seed_state(&fip->rnd_state, fip->lp->wwpn);
+	fcoe_ctlr_vn_restart(fip);
+}
+
+/**
+ * fcoe_ctlr_vn_parse - parse probe request or response
+ * @fip: The FCoE controller
+ * @skb: incoming packet
+ * @rdata: buffer for resulting parsed VN entry plus fcoe_rport
+ *
+ * Returns non-zero error number on error.
+ * Does not consume the packet.
+ */
+static int fcoe_ctlr_vn_parse(struct fcoe_ctlr *fip,
+			      struct sk_buff *skb,
+			      struct fc_rport_priv *rdata)
+{
+	struct fip_header *fiph;
+	struct fip_desc *desc = NULL;
+	struct fip_mac_desc *macd = NULL;
+	struct fip_wwn_desc *wwn = NULL;
+	struct fip_vn_desc *vn = NULL;
+	struct fip_size_desc *size = NULL;
+	struct fcoe_rport *frport;
+	size_t rlen;
+	size_t dlen;
+	u32 desc_mask = 0;
+	u32 dtype;
+	u8 sub;
+
+	memset(rdata, 0, sizeof(*rdata) + sizeof(*frport));
+	frport = fcoe_ctlr_rport(rdata);
+
+	fiph = (struct fip_header *)skb->data;
+	frport->flags = ntohs(fiph->fip_flags);
+
+	sub = fiph->fip_subcode;
+	switch (sub) {
+	case FIP_SC_VN_PROBE_REQ:
+	case FIP_SC_VN_PROBE_REP:
+	case FIP_SC_VN_BEACON:
+		desc_mask = BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME) |
+			    BIT(FIP_DT_VN_ID);
+		break;
+	case FIP_SC_VN_CLAIM_NOTIFY:
+	case FIP_SC_VN_CLAIM_REP:
+		desc_mask = BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME) |
+			    BIT(FIP_DT_VN_ID) | BIT(FIP_DT_FC4F) |
+			    BIT(FIP_DT_FCOE_SIZE);
+		break;
+	default:
+		LIBFCOE_FIP_DBG(fip, "vn_parse unknown subcode %u\n", sub);
+		return -EINVAL;
+	}
+
+	rlen = ntohs(fiph->fip_dl_len) * 4;
+	if (rlen + sizeof(*fiph) > skb->len)
+		return -EINVAL;
+
+	desc = (struct fip_desc *)(fiph + 1);
+	while (rlen > 0) {
+		dlen = desc->fip_dlen * FIP_BPW;
+		if (dlen < sizeof(*desc) || dlen > rlen)
+			return -EINVAL;
+
+		dtype = desc->fip_dtype;
+		if (dtype < 32) {
+			if (!(desc_mask & BIT(dtype))) {
+				LIBFCOE_FIP_DBG(fip,
+						"unexpected or duplicated desc "
+						"desc type %u in "
+						"FIP VN2VN subtype %u\n",
+						dtype, sub);
+				return -EINVAL;
+			}
+			desc_mask &= ~BIT(dtype);
+		}
+
+		switch (dtype) {
+		case FIP_DT_MAC:
+			if (dlen != sizeof(struct fip_mac_desc))
+				goto len_err;
+			macd = (struct fip_mac_desc *)desc;
+			if (!is_valid_ether_addr(macd->fd_mac)) {
+				LIBFCOE_FIP_DBG(fip,
+					"Invalid MAC addr %pM in FIP VN2VN\n",
+					 macd->fd_mac);
+				return -EINVAL;
+			}
+			memcpy(frport->enode_mac, macd->fd_mac, ETH_ALEN);
+			break;
+		case FIP_DT_NAME:
+			if (dlen != sizeof(struct fip_wwn_desc))
+				goto len_err;
+			wwn = (struct fip_wwn_desc *)desc;
+			rdata->ids.node_name = get_unaligned_be64(&wwn->fd_wwn);
+			break;
+		case FIP_DT_VN_ID:
+			if (dlen != sizeof(struct fip_vn_desc))
+				goto len_err;
+			vn = (struct fip_vn_desc *)desc;
+			memcpy(frport->vn_mac, vn->fd_mac, ETH_ALEN);
+			rdata->ids.port_id = ntoh24(vn->fd_fc_id);
+			rdata->ids.port_name = get_unaligned_be64(&vn->fd_wwpn);
+			break;
+		case FIP_DT_FC4F:
+			if (dlen != sizeof(struct fip_fc4_feat))
+				goto len_err;
+			break;
+		case FIP_DT_FCOE_SIZE:
+			if (dlen != sizeof(struct fip_size_desc))
+				goto len_err;
+			size = (struct fip_size_desc *)desc;
+			frport->fcoe_len = ntohs(size->fd_size);
+			break;
+		default:
+			LIBFCOE_FIP_DBG(fip, "unexpected descriptor type %x "
+					"in FIP probe\n", dtype);
+			/* standard says ignore unknown descriptors >= 128 */
+			if (dtype < FIP_DT_NON_CRITICAL)
+				return -EINVAL;
+			break;
+		}
+		desc = (struct fip_desc *)((char *)desc + dlen);
+		rlen -= dlen;
+	}
+	return 0;
+
+len_err:
+	LIBFCOE_FIP_DBG(fip, "FIP length error in descriptor type %x len %zu\n",
+			dtype, dlen);
+	return -EINVAL;
+}
+
+/**
+ * fcoe_ctlr_vn_send_claim() - send multicast FIP VN2VN Claim Notification.
+ * @fip: The FCoE controller
+ *
+ * Called with ctlr_mutex held.
+ */
+static void fcoe_ctlr_vn_send_claim(struct fcoe_ctlr *fip)
+{
+	fcoe_ctlr_vn_send(fip, FIP_SC_VN_CLAIM_NOTIFY, fcoe_all_vn2vn, 0);
+	fip->sol_time = jiffies;
+}
+
+/**
+ * fcoe_ctlr_vn_probe_req() - handle incoming VN2VN probe request.
+ * @fip: The FCoE controller
+ * @rdata: parsed remote port with frport from the probe request
+ *
+ * Called with ctlr_mutex held.
+ */
+static void fcoe_ctlr_vn_probe_req(struct fcoe_ctlr *fip,
+				   struct fc_rport_priv *rdata)
+{
+	struct fcoe_rport *frport = fcoe_ctlr_rport(rdata);
+
+	if (rdata->ids.port_id != fip->port_id)
+		return;
+
+	switch (fip->state) {
+	case FIP_ST_VNMP_CLAIM:
+	case FIP_ST_VNMP_UP:
+		LIBFCOE_FIP_DBG(fip, "vn_probe_req: send reply, state %x\n",
+				fip->state);
+		fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REP,
+				  frport->enode_mac, 0);
+		break;
+	case FIP_ST_VNMP_PROBE1:
+	case FIP_ST_VNMP_PROBE2:
+		/*
+		 * Decide whether to reply to the Probe.
+		 * Our selected address is never a "recorded" one, so
+		 * only reply if our WWPN is greater and the
+		 * Probe's REC bit is not set.
+		 * If we don't reply, we will change our address.
+		 */
+		if (fip->lp->wwpn > rdata->ids.port_name &&
+		    !(frport->flags & FIP_FL_REC_OR_P2P)) {
+			LIBFCOE_FIP_DBG(fip, "vn_probe_req: "
+					"port_id collision\n");
+			fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REP,
+					  frport->enode_mac, 0);
+			break;
+		}
+		/* fall through */
+	case FIP_ST_VNMP_START:
+		LIBFCOE_FIP_DBG(fip, "vn_probe_req: "
+				"restart VN2VN negotiation\n");
+		fcoe_ctlr_vn_restart(fip);
+		break;
+	default:
+		LIBFCOE_FIP_DBG(fip, "vn_probe_req: ignore state %x\n",
+				fip->state);
+		break;
+	}
+}
+
+/**
+ * fcoe_ctlr_vn_probe_reply() - handle incoming VN2VN probe reply.
+ * @fip: The FCoE controller
+ * @rdata: parsed remote port with frport from the probe request
+ *
+ * Called with ctlr_mutex held.
+ */
+static void fcoe_ctlr_vn_probe_reply(struct fcoe_ctlr *fip,
+				   struct fc_rport_priv *rdata)
+{
+	if (rdata->ids.port_id != fip->port_id)
+		return;
+	switch (fip->state) {
+	case FIP_ST_VNMP_START:
+	case FIP_ST_VNMP_PROBE1:
+	case FIP_ST_VNMP_PROBE2:
+	case FIP_ST_VNMP_CLAIM:
+		LIBFCOE_FIP_DBG(fip, "vn_probe_reply: restart state %x\n",
+				fip->state);
+		fcoe_ctlr_vn_restart(fip);
+		break;
+	case FIP_ST_VNMP_UP:
+		LIBFCOE_FIP_DBG(fip, "vn_probe_reply: send claim notify\n");
+		fcoe_ctlr_vn_send_claim(fip);
+		break;
+	default:
+		break;
+	}
+}
+
+/**
+ * fcoe_ctlr_vn_add() - Add a VN2VN entry to the list, based on a claim reply.
+ * @fip: The FCoE controller
+ * @new: newly-parsed remote port with frport as a template for new rdata
+ *
+ * Called with ctlr_mutex held.
+ */
+static void fcoe_ctlr_vn_add(struct fcoe_ctlr *fip, struct fc_rport_priv *new)
+{
+	struct fc_lport *lport = fip->lp;
+	struct fc_rport_priv *rdata;
+	struct fc_rport_identifiers *ids;
+	struct fcoe_rport *frport;
+	u32 port_id;
+
+	port_id = new->ids.port_id;
+	if (port_id == fip->port_id)
+		return;
+
+	mutex_lock(&lport->disc.disc_mutex);
+	rdata = fc_rport_create(lport, port_id);
+	if (!rdata) {
+		mutex_unlock(&lport->disc.disc_mutex);
+		return;
+	}
+	mutex_lock(&rdata->rp_mutex);
+	mutex_unlock(&lport->disc.disc_mutex);
+
+	rdata->ops = &fcoe_ctlr_vn_rport_ops;
+	rdata->disc_id = lport->disc.disc_id;
+
+	ids = &rdata->ids;
+	if ((ids->port_name != -1 && ids->port_name != new->ids.port_name) ||
+	    (ids->node_name != -1 && ids->node_name != new->ids.node_name)) {
+		mutex_unlock(&rdata->rp_mutex);
+		LIBFCOE_FIP_DBG(fip, "vn_add rport logoff %6.6x\n", port_id);
+		fc_rport_logoff(rdata);
+		mutex_lock(&rdata->rp_mutex);
+	}
+	ids->port_name = new->ids.port_name;
+	ids->node_name = new->ids.node_name;
+	mutex_unlock(&rdata->rp_mutex);
+
+	frport = fcoe_ctlr_rport(rdata);
+	LIBFCOE_FIP_DBG(fip, "vn_add rport %6.6x %s state %d\n",
+			port_id, frport->fcoe_len ? "old" : "new",
+			rdata->rp_state);
+	*frport = *fcoe_ctlr_rport(new);
+	frport->time = 0;
+}
+
+/**
+ * fcoe_ctlr_vn_lookup() - Find VN remote port's MAC address
+ * @fip: The FCoE controller
+ * @port_id:  The port_id of the remote VN_node
+ * @mac: buffer which will hold the VN_NODE destination MAC address, if found.
+ *
+ * Returns non-zero error if no remote port found.
+ */
+static int fcoe_ctlr_vn_lookup(struct fcoe_ctlr *fip, u32 port_id, u8 *mac)
+{
+	struct fc_lport *lport = fip->lp;
+	struct fc_rport_priv *rdata;
+	struct fcoe_rport *frport;
+	int ret = -1;
+
+	rdata = fc_rport_lookup(lport, port_id);
+	if (rdata) {
+		frport = fcoe_ctlr_rport(rdata);
+		memcpy(mac, frport->enode_mac, ETH_ALEN);
+		ret = 0;
+		kref_put(&rdata->kref, fc_rport_destroy);
+	}
+	return ret;
+}
+
+/**
+ * fcoe_ctlr_vn_claim_notify() - handle received FIP VN2VN Claim Notification
+ * @fip: The FCoE controller
+ * @new: newly-parsed remote port with frport as a template for new rdata
+ *
+ * Called with ctlr_mutex held.
+ */
+static void fcoe_ctlr_vn_claim_notify(struct fcoe_ctlr *fip,
+				      struct fc_rport_priv *new)
+{
+	struct fcoe_rport *frport = fcoe_ctlr_rport(new);
+
+	if (frport->flags & FIP_FL_REC_OR_P2P) {
+		LIBFCOE_FIP_DBG(fip, "send probe req for P2P/REC\n");
+		fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0);
+		return;
+	}
+	switch (fip->state) {
+	case FIP_ST_VNMP_START:
+	case FIP_ST_VNMP_PROBE1:
+	case FIP_ST_VNMP_PROBE2:
+		if (new->ids.port_id == fip->port_id) {
+			LIBFCOE_FIP_DBG(fip, "vn_claim_notify: "
+					"restart, state %d\n",
+					fip->state);
+			fcoe_ctlr_vn_restart(fip);
+		}
+		break;
+	case FIP_ST_VNMP_CLAIM:
+	case FIP_ST_VNMP_UP:
+		if (new->ids.port_id == fip->port_id) {
+			if (new->ids.port_name > fip->lp->wwpn) {
+				LIBFCOE_FIP_DBG(fip, "vn_claim_notify: "
+						"restart, port_id collision\n");
+				fcoe_ctlr_vn_restart(fip);
+				break;
+			}
+			LIBFCOE_FIP_DBG(fip, "vn_claim_notify: "
+					"send claim notify\n");
+			fcoe_ctlr_vn_send_claim(fip);
+			break;
+		}
+		LIBFCOE_FIP_DBG(fip, "vn_claim_notify: send reply to %x\n",
+				new->ids.port_id);
+		fcoe_ctlr_vn_send(fip, FIP_SC_VN_CLAIM_REP, frport->enode_mac,
+				  min((u32)frport->fcoe_len,
+				      fcoe_ctlr_fcoe_size(fip)));
+		fcoe_ctlr_vn_add(fip, new);
+		break;
+	default:
+		LIBFCOE_FIP_DBG(fip, "vn_claim_notify: "
+				"ignoring claim from %x\n", new->ids.port_id);
+		break;
+	}
+}
+
+/**
+ * fcoe_ctlr_vn_claim_resp() - handle received Claim Response
+ * @fip: The FCoE controller that received the frame
+ * @new: newly-parsed remote port with frport from the Claim Response
+ *
+ * Called with ctlr_mutex held.
+ */
+static void fcoe_ctlr_vn_claim_resp(struct fcoe_ctlr *fip,
+				    struct fc_rport_priv *new)
+{
+	LIBFCOE_FIP_DBG(fip, "claim resp from from rport %x - state %s\n",
+			new->ids.port_id, fcoe_ctlr_state(fip->state));
+	if (fip->state == FIP_ST_VNMP_UP || fip->state == FIP_ST_VNMP_CLAIM)
+		fcoe_ctlr_vn_add(fip, new);
+}
+
+/**
+ * fcoe_ctlr_vn_beacon() - handle received beacon.
+ * @fip: The FCoE controller that received the frame
+ * @new: newly-parsed remote port with frport from the Beacon
+ *
+ * Called with ctlr_mutex held.
+ */
+static void fcoe_ctlr_vn_beacon(struct fcoe_ctlr *fip,
+				struct fc_rport_priv *new)
+{
+	struct fc_lport *lport = fip->lp;
+	struct fc_rport_priv *rdata;
+	struct fcoe_rport *frport;
+
+	frport = fcoe_ctlr_rport(new);
+	if (frport->flags & FIP_FL_REC_OR_P2P) {
+		LIBFCOE_FIP_DBG(fip, "p2p beacon while in vn2vn mode\n");
+		fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0);
+		return;
+	}
+	rdata = fc_rport_lookup(lport, new->ids.port_id);
+	if (rdata) {
+		if (rdata->ids.node_name == new->ids.node_name &&
+		    rdata->ids.port_name == new->ids.port_name) {
+			frport = fcoe_ctlr_rport(rdata);
+			LIBFCOE_FIP_DBG(fip, "beacon from rport %x\n",
+					rdata->ids.port_id);
+			if (!frport->time && fip->state == FIP_ST_VNMP_UP) {
+				LIBFCOE_FIP_DBG(fip, "beacon expired "
+						"for rport %x\n",
+						rdata->ids.port_id);
+				fc_rport_login(rdata);
+			}
+			frport->time = jiffies;
+		}
+		kref_put(&rdata->kref, fc_rport_destroy);
+		return;
+	}
+	if (fip->state != FIP_ST_VNMP_UP)
+		return;
+
+	/*
+	 * Beacon from a new neighbor.
+	 * Send a claim notify if one hasn't been sent recently.
+	 * Don't add the neighbor yet.
+	 */
+	LIBFCOE_FIP_DBG(fip, "beacon from new rport %x. sending claim notify\n",
+			new->ids.port_id);
+	if (time_after(jiffies,
+		       fip->sol_time + msecs_to_jiffies(FIP_VN_ANN_WAIT)))
+		fcoe_ctlr_vn_send_claim(fip);
+}
+
+/**
+ * fcoe_ctlr_vn_age() - Check for VN_ports without recent beacons
+ * @fip: The FCoE controller
+ *
+ * Called with ctlr_mutex held.
+ * Called only in state FIP_ST_VNMP_UP.
+ * Returns the soonest time for next age-out or a time far in the future.
+ */
+static unsigned long fcoe_ctlr_vn_age(struct fcoe_ctlr *fip)
+{
+	struct fc_lport *lport = fip->lp;
+	struct fc_rport_priv *rdata;
+	struct fcoe_rport *frport;
+	unsigned long next_time;
+	unsigned long deadline;
+
+	next_time = jiffies + msecs_to_jiffies(FIP_VN_BEACON_INT * 10);
+	mutex_lock(&lport->disc.disc_mutex);
+	list_for_each_entry_rcu(rdata, &lport->disc.rports, peers) {
+		if (!kref_get_unless_zero(&rdata->kref))
+			continue;
+		frport = fcoe_ctlr_rport(rdata);
+		if (!frport->time) {
+			kref_put(&rdata->kref, fc_rport_destroy);
+			continue;
+		}
+		deadline = frport->time +
+			   msecs_to_jiffies(FIP_VN_BEACON_INT * 25 / 10);
+		if (time_after_eq(jiffies, deadline)) {
+			frport->time = 0;
+			LIBFCOE_FIP_DBG(fip,
+				"port %16.16llx fc_id %6.6x beacon expired\n",
+				rdata->ids.port_name, rdata->ids.port_id);
+			fc_rport_logoff(rdata);
+		} else if (time_before(deadline, next_time))
+			next_time = deadline;
+		kref_put(&rdata->kref, fc_rport_destroy);
+	}
+	mutex_unlock(&lport->disc.disc_mutex);
+	return next_time;
+}
+
+/**
+ * fcoe_ctlr_vn_recv() - Receive a FIP frame
+ * @fip: The FCoE controller that received the frame
+ * @skb: The received FIP frame
+ *
+ * Returns non-zero if the frame is dropped.
+ * Always consumes the frame.
+ */
+static int fcoe_ctlr_vn_recv(struct fcoe_ctlr *fip, struct sk_buff *skb)
+{
+	struct fip_header *fiph;
+	enum fip_vn2vn_subcode sub;
+	struct {
+		struct fc_rport_priv rdata;
+		struct fcoe_rport frport;
+	} buf;
+	int rc, vlan_id = 0;
+
+	fiph = (struct fip_header *)skb->data;
+	sub = fiph->fip_subcode;
+
+	if (fip->lp->vlan)
+		vlan_id = skb_vlan_tag_get_id(skb);
+
+	if (vlan_id && vlan_id != fip->lp->vlan) {
+		LIBFCOE_FIP_DBG(fip, "vn_recv drop frame sub %x vlan %d\n",
+				sub, vlan_id);
+		rc = -EAGAIN;
+		goto drop;
+	}
+
+	rc = fcoe_ctlr_vn_parse(fip, skb, &buf.rdata);
+	if (rc) {
+		LIBFCOE_FIP_DBG(fip, "vn_recv vn_parse error %d\n", rc);
+		goto drop;
+	}
+
+	mutex_lock(&fip->ctlr_mutex);
+	switch (sub) {
+	case FIP_SC_VN_PROBE_REQ:
+		fcoe_ctlr_vn_probe_req(fip, &buf.rdata);
+		break;
+	case FIP_SC_VN_PROBE_REP:
+		fcoe_ctlr_vn_probe_reply(fip, &buf.rdata);
+		break;
+	case FIP_SC_VN_CLAIM_NOTIFY:
+		fcoe_ctlr_vn_claim_notify(fip, &buf.rdata);
+		break;
+	case FIP_SC_VN_CLAIM_REP:
+		fcoe_ctlr_vn_claim_resp(fip, &buf.rdata);
+		break;
+	case FIP_SC_VN_BEACON:
+		fcoe_ctlr_vn_beacon(fip, &buf.rdata);
+		break;
+	default:
+		LIBFCOE_FIP_DBG(fip, "vn_recv unknown subcode %d\n", sub);
+		rc = -1;
+		break;
+	}
+	mutex_unlock(&fip->ctlr_mutex);
+drop:
+	kfree_skb(skb);
+	return rc;
+}
+
+/**
+ * fcoe_ctlr_vlan_parse - parse vlan discovery request or response
+ * @fip: The FCoE controller
+ * @skb: incoming packet
+ * @rdata: buffer for resulting parsed VLAN entry plus fcoe_rport
+ *
+ * Returns non-zero error number on error.
+ * Does not consume the packet.
+ */
+static int fcoe_ctlr_vlan_parse(struct fcoe_ctlr *fip,
+			      struct sk_buff *skb,
+			      struct fc_rport_priv *rdata)
+{
+	struct fip_header *fiph;
+	struct fip_desc *desc = NULL;
+	struct fip_mac_desc *macd = NULL;
+	struct fip_wwn_desc *wwn = NULL;
+	struct fcoe_rport *frport;
+	size_t rlen;
+	size_t dlen;
+	u32 desc_mask = 0;
+	u32 dtype;
+	u8 sub;
+
+	memset(rdata, 0, sizeof(*rdata) + sizeof(*frport));
+	frport = fcoe_ctlr_rport(rdata);
+
+	fiph = (struct fip_header *)skb->data;
+	frport->flags = ntohs(fiph->fip_flags);
+
+	sub = fiph->fip_subcode;
+	switch (sub) {
+	case FIP_SC_VL_REQ:
+		desc_mask = BIT(FIP_DT_MAC) | BIT(FIP_DT_NAME);
+		break;
+	default:
+		LIBFCOE_FIP_DBG(fip, "vn_parse unknown subcode %u\n", sub);
+		return -EINVAL;
+	}
+
+	rlen = ntohs(fiph->fip_dl_len) * 4;
+	if (rlen + sizeof(*fiph) > skb->len)
+		return -EINVAL;
+
+	desc = (struct fip_desc *)(fiph + 1);
+	while (rlen > 0) {
+		dlen = desc->fip_dlen * FIP_BPW;
+		if (dlen < sizeof(*desc) || dlen > rlen)
+			return -EINVAL;
+
+		dtype = desc->fip_dtype;
+		if (dtype < 32) {
+			if (!(desc_mask & BIT(dtype))) {
+				LIBFCOE_FIP_DBG(fip,
+						"unexpected or duplicated desc "
+						"desc type %u in "
+						"FIP VN2VN subtype %u\n",
+						dtype, sub);
+				return -EINVAL;
+			}
+			desc_mask &= ~BIT(dtype);
+		}
+
+		switch (dtype) {
+		case FIP_DT_MAC:
+			if (dlen != sizeof(struct fip_mac_desc))
+				goto len_err;
+			macd = (struct fip_mac_desc *)desc;
+			if (!is_valid_ether_addr(macd->fd_mac)) {
+				LIBFCOE_FIP_DBG(fip,
+					"Invalid MAC addr %pM in FIP VN2VN\n",
+					 macd->fd_mac);
+				return -EINVAL;
+			}
+			memcpy(frport->enode_mac, macd->fd_mac, ETH_ALEN);
+			break;
+		case FIP_DT_NAME:
+			if (dlen != sizeof(struct fip_wwn_desc))
+				goto len_err;
+			wwn = (struct fip_wwn_desc *)desc;
+			rdata->ids.node_name = get_unaligned_be64(&wwn->fd_wwn);
+			break;
+		default:
+			LIBFCOE_FIP_DBG(fip, "unexpected descriptor type %x "
+					"in FIP probe\n", dtype);
+			/* standard says ignore unknown descriptors >= 128 */
+			if (dtype < FIP_DT_NON_CRITICAL)
+				return -EINVAL;
+			break;
+		}
+		desc = (struct fip_desc *)((char *)desc + dlen);
+		rlen -= dlen;
+	}
+	return 0;
+
+len_err:
+	LIBFCOE_FIP_DBG(fip, "FIP length error in descriptor type %x len %zu\n",
+			dtype, dlen);
+	return -EINVAL;
+}
+
+/**
+ * fcoe_ctlr_vlan_send() - Send a FIP VLAN Notification
+ * @fip: The FCoE controller
+ * @sub: sub-opcode for vlan notification or vn2vn vlan notification
+ * @dest: The destination Ethernet MAC address
+ * @min_len: minimum size of the Ethernet payload to be sent
+ */
+static void fcoe_ctlr_vlan_send(struct fcoe_ctlr *fip,
+			      enum fip_vlan_subcode sub,
+			      const u8 *dest)
+{
+	struct sk_buff *skb;
+	struct fip_vlan_notify_frame {
+		struct ethhdr eth;
+		struct fip_header fip;
+		struct fip_mac_desc mac;
+		struct fip_vlan_desc vlan;
+	} __packed * frame;
+	size_t len;
+	size_t dlen;
+
+	len = sizeof(*frame);
+	dlen = sizeof(frame->mac) + sizeof(frame->vlan);
+	len = max(len, sizeof(struct ethhdr));
+
+	skb = dev_alloc_skb(len);
+	if (!skb)
+		return;
+
+	LIBFCOE_FIP_DBG(fip, "fip %s vlan notification, vlan %d\n",
+			fip->mode == FIP_MODE_VN2VN ? "vn2vn" : "fcf",
+			fip->lp->vlan);
+
+	frame = (struct fip_vlan_notify_frame *)skb->data;
+	memset(frame, 0, len);
+	memcpy(frame->eth.h_dest, dest, ETH_ALEN);
+
+	memcpy(frame->eth.h_source, fip->ctl_src_addr, ETH_ALEN);
+	frame->eth.h_proto = htons(ETH_P_FIP);
+
+	frame->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER);
+	frame->fip.fip_op = htons(FIP_OP_VLAN);
+	frame->fip.fip_subcode = sub;
+	frame->fip.fip_dl_len = htons(dlen / FIP_BPW);
+
+	frame->mac.fd_desc.fip_dtype = FIP_DT_MAC;
+	frame->mac.fd_desc.fip_dlen = sizeof(frame->mac) / FIP_BPW;
+	memcpy(frame->mac.fd_mac, fip->ctl_src_addr, ETH_ALEN);
+
+	frame->vlan.fd_desc.fip_dtype = FIP_DT_VLAN;
+	frame->vlan.fd_desc.fip_dlen = sizeof(frame->vlan) / FIP_BPW;
+	put_unaligned_be16(fip->lp->vlan, &frame->vlan.fd_vlan);
+
+	skb_put(skb, len);
+	skb->protocol = htons(ETH_P_FIP);
+	skb->priority = fip->priority;
+	skb_reset_mac_header(skb);
+	skb_reset_network_header(skb);
+
+	fip->send(fip, skb);
+}
+
+/**
+ * fcoe_ctlr_vlan_disk_reply() - send FIP VLAN Discovery Notification.
+ * @fip: The FCoE controller
+ *
+ * Called with ctlr_mutex held.
+ */
+static void fcoe_ctlr_vlan_disc_reply(struct fcoe_ctlr *fip,
+				      struct fc_rport_priv *rdata)
+{
+	struct fcoe_rport *frport = fcoe_ctlr_rport(rdata);
+	enum fip_vlan_subcode sub = FIP_SC_VL_NOTE;
+
+	if (fip->mode == FIP_MODE_VN2VN)
+		sub = FIP_SC_VL_VN2VN_NOTE;
+
+	fcoe_ctlr_vlan_send(fip, sub, frport->enode_mac);
+}
+
+/**
+ * fcoe_ctlr_vlan_recv - vlan request receive handler for VN2VN mode.
+ * @lport: The local port
+ * @fp: The received frame
+ *
+ */
+static int fcoe_ctlr_vlan_recv(struct fcoe_ctlr *fip, struct sk_buff *skb)
+{
+	struct fip_header *fiph;
+	enum fip_vlan_subcode sub;
+	struct {
+		struct fc_rport_priv rdata;
+		struct fcoe_rport frport;
+	} buf;
+	int rc;
+
+	fiph = (struct fip_header *)skb->data;
+	sub = fiph->fip_subcode;
+	rc = fcoe_ctlr_vlan_parse(fip, skb, &buf.rdata);
+	if (rc) {
+		LIBFCOE_FIP_DBG(fip, "vlan_recv vlan_parse error %d\n", rc);
+		goto drop;
+	}
+	mutex_lock(&fip->ctlr_mutex);
+	if (sub == FIP_SC_VL_REQ)
+		fcoe_ctlr_vlan_disc_reply(fip, &buf.rdata);
+	mutex_unlock(&fip->ctlr_mutex);
+
+drop:
+	kfree_skb(skb);
+	return rc;
+}
+
+/**
+ * fcoe_ctlr_disc_recv - discovery receive handler for VN2VN mode.
+ * @lport: The local port
+ * @fp: The received frame
+ *
+ * This should never be called since we don't see RSCNs or other
+ * fabric-generated ELSes.
+ */
+static void fcoe_ctlr_disc_recv(struct fc_lport *lport, struct fc_frame *fp)
+{
+	struct fc_seq_els_data rjt_data;
+
+	rjt_data.reason = ELS_RJT_UNSUP;
+	rjt_data.explan = ELS_EXPL_NONE;
+	fc_seq_els_rsp_send(fp, ELS_LS_RJT, &rjt_data);
+	fc_frame_free(fp);
+}
+
+/**
+ * fcoe_ctlr_disc_recv - start discovery for VN2VN mode.
+ * @fip: The FCoE controller
+ *
+ * This sets a flag indicating that remote ports should be created
+ * and started for the peers we discover.  We use the disc_callback
+ * pointer as that flag.  Peers already discovered are created here.
+ *
+ * The lport lock is held during this call. The callback must be done
+ * later, without holding either the lport or discovery locks.
+ * The fcoe_ctlr lock may also be held during this call.
+ */
+static void fcoe_ctlr_disc_start(void (*callback)(struct fc_lport *,
+						  enum fc_disc_event),
+				 struct fc_lport *lport)
+{
+	struct fc_disc *disc = &lport->disc;
+	struct fcoe_ctlr *fip = disc->priv;
+
+	mutex_lock(&disc->disc_mutex);
+	disc->disc_callback = callback;
+	disc->disc_id = (disc->disc_id + 2) | 1;
+	disc->pending = 1;
+	schedule_work(&fip->timer_work);
+	mutex_unlock(&disc->disc_mutex);
+}
+
+/**
+ * fcoe_ctlr_vn_disc() - report FIP VN_port discovery results after claim state.
+ * @fip: The FCoE controller
+ *
+ * Starts the FLOGI and PLOGI login process to each discovered rport for which
+ * we've received at least one beacon.
+ * Performs the discovery complete callback.
+ */
+static void fcoe_ctlr_vn_disc(struct fcoe_ctlr *fip)
+{
+	struct fc_lport *lport = fip->lp;
+	struct fc_disc *disc = &lport->disc;
+	struct fc_rport_priv *rdata;
+	struct fcoe_rport *frport;
+	void (*callback)(struct fc_lport *, enum fc_disc_event);
+
+	mutex_lock(&disc->disc_mutex);
+	callback = disc->pending ? disc->disc_callback : NULL;
+	disc->pending = 0;
+	list_for_each_entry_rcu(rdata, &disc->rports, peers) {
+		if (!kref_get_unless_zero(&rdata->kref))
+			continue;
+		frport = fcoe_ctlr_rport(rdata);
+		if (frport->time)
+			fc_rport_login(rdata);
+		kref_put(&rdata->kref, fc_rport_destroy);
+	}
+	mutex_unlock(&disc->disc_mutex);
+	if (callback)
+		callback(lport, DISC_EV_SUCCESS);
+}
+
+/**
+ * fcoe_ctlr_vn_timeout - timer work function for VN2VN mode.
+ * @fip: The FCoE controller
+ */
+static void fcoe_ctlr_vn_timeout(struct fcoe_ctlr *fip)
+{
+	unsigned long next_time;
+	u8 mac[ETH_ALEN];
+	u32 new_port_id = 0;
+
+	mutex_lock(&fip->ctlr_mutex);
+	switch (fip->state) {
+	case FIP_ST_VNMP_START:
+		fcoe_ctlr_set_state(fip, FIP_ST_VNMP_PROBE1);
+		LIBFCOE_FIP_DBG(fip, "vn_timeout: send 1st probe request\n");
+		fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0);
+		next_time = jiffies + msecs_to_jiffies(FIP_VN_PROBE_WAIT);
+		break;
+	case FIP_ST_VNMP_PROBE1:
+		fcoe_ctlr_set_state(fip, FIP_ST_VNMP_PROBE2);
+		LIBFCOE_FIP_DBG(fip, "vn_timeout: send 2nd probe request\n");
+		fcoe_ctlr_vn_send(fip, FIP_SC_VN_PROBE_REQ, fcoe_all_vn2vn, 0);
+		next_time = jiffies + msecs_to_jiffies(FIP_VN_ANN_WAIT);
+		break;
+	case FIP_ST_VNMP_PROBE2:
+		fcoe_ctlr_set_state(fip, FIP_ST_VNMP_CLAIM);
+		new_port_id = fip->port_id;
+		hton24(mac, FIP_VN_FC_MAP);
+		hton24(mac + 3, new_port_id);
+		fcoe_ctlr_map_dest(fip);
+		fip->update_mac(fip->lp, mac);
+		LIBFCOE_FIP_DBG(fip, "vn_timeout: send claim notify\n");
+		fcoe_ctlr_vn_send_claim(fip);
+		next_time = jiffies + msecs_to_jiffies(FIP_VN_ANN_WAIT);
+		break;
+	case FIP_ST_VNMP_CLAIM:
+		/*
+		 * This may be invoked either by starting discovery so don't
+		 * go to the next state unless it's been long enough.
+		 */
+		next_time = fip->sol_time + msecs_to_jiffies(FIP_VN_ANN_WAIT);
+		if (time_after_eq(jiffies, next_time)) {
+			fcoe_ctlr_set_state(fip, FIP_ST_VNMP_UP);
+			LIBFCOE_FIP_DBG(fip, "vn_timeout: send vn2vn beacon\n");
+			fcoe_ctlr_vn_send(fip, FIP_SC_VN_BEACON,
+					  fcoe_all_vn2vn, 0);
+			next_time = jiffies + msecs_to_jiffies(FIP_VN_ANN_WAIT);
+			fip->port_ka_time = next_time;
+		}
+		fcoe_ctlr_vn_disc(fip);
+		break;
+	case FIP_ST_VNMP_UP:
+		next_time = fcoe_ctlr_vn_age(fip);
+		if (time_after_eq(jiffies, fip->port_ka_time)) {
+			LIBFCOE_FIP_DBG(fip, "vn_timeout: send vn2vn beacon\n");
+			fcoe_ctlr_vn_send(fip, FIP_SC_VN_BEACON,
+					  fcoe_all_vn2vn, 0);
+			fip->port_ka_time = jiffies +
+				 msecs_to_jiffies(FIP_VN_BEACON_INT +
+					(prandom_u32() % FIP_VN_BEACON_FUZZ));
+		}
+		if (time_before(fip->port_ka_time, next_time))
+			next_time = fip->port_ka_time;
+		break;
+	case FIP_ST_LINK_WAIT:
+		goto unlock;
+	default:
+		WARN(1, "unexpected state %d\n", fip->state);
+		goto unlock;
+	}
+	mod_timer(&fip->timer, next_time);
+unlock:
+	mutex_unlock(&fip->ctlr_mutex);
+
+	/* If port ID is new, notify local port after dropping ctlr_mutex */
+	if (new_port_id)
+		fc_lport_set_local_id(fip->lp, new_port_id);
+}
+
+/**
+ * fcoe_ctlr_mode_set() - Set or reset the ctlr's mode
+ * @lport: The local port to be (re)configured
+ * @fip:   The FCoE controller whose mode is changing
+ * @fip_mode: The new fip mode
+ *
+ * Note that the we shouldn't be changing the libfc discovery settings
+ * (fc_disc_config) while an lport is going through the libfc state
+ * machine. The mode can only be changed when a fcoe_ctlr device is
+ * disabled, so that should ensure that this routine is only called
+ * when nothing is happening.
+ */
+static void fcoe_ctlr_mode_set(struct fc_lport *lport, struct fcoe_ctlr *fip,
+			       enum fip_mode fip_mode)
+{
+	void *priv;
+
+	WARN_ON(lport->state != LPORT_ST_RESET &&
+		lport->state != LPORT_ST_DISABLED);
+
+	if (fip_mode == FIP_MODE_VN2VN) {
+		lport->rport_priv_size = sizeof(struct fcoe_rport);
+		lport->point_to_multipoint = 1;
+		lport->tt.disc_recv_req = fcoe_ctlr_disc_recv;
+		lport->tt.disc_start = fcoe_ctlr_disc_start;
+		lport->tt.disc_stop = fcoe_ctlr_disc_stop;
+		lport->tt.disc_stop_final = fcoe_ctlr_disc_stop_final;
+		priv = fip;
+	} else {
+		lport->rport_priv_size = 0;
+		lport->point_to_multipoint = 0;
+		lport->tt.disc_recv_req = NULL;
+		lport->tt.disc_start = NULL;
+		lport->tt.disc_stop = NULL;
+		lport->tt.disc_stop_final = NULL;
+		priv = lport;
+	}
+
+	fc_disc_config(lport, priv);
+}
+
+/**
+ * fcoe_libfc_config() - Sets up libfc related properties for local port
+ * @lport:    The local port to configure libfc for
+ * @fip:      The FCoE controller in use by the local port
+ * @tt:       The libfc function template
+ * @init_fcp: If non-zero, the FCP portion of libfc should be initialized
+ *
+ * Returns : 0 for success
+ */
+int fcoe_libfc_config(struct fc_lport *lport, struct fcoe_ctlr *fip,
+		      const struct libfc_function_template *tt, int init_fcp)
+{
+	/* Set the function pointers set by the LLDD */
+	memcpy(&lport->tt, tt, sizeof(*tt));
+	if (init_fcp && fc_fcp_init(lport))
+		return -ENOMEM;
+	fc_exch_init(lport);
+	fc_elsct_init(lport);
+	fc_lport_init(lport);
+	fc_disc_init(lport);
+	fcoe_ctlr_mode_set(lport, fip, fip->mode);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(fcoe_libfc_config);
+
+void fcoe_fcf_get_selected(struct fcoe_fcf_device *fcf_dev)
+{
+	struct fcoe_ctlr_device *ctlr_dev = fcoe_fcf_dev_to_ctlr_dev(fcf_dev);
+	struct fcoe_ctlr *fip = fcoe_ctlr_device_priv(ctlr_dev);
+	struct fcoe_fcf *fcf;
+
+	mutex_lock(&fip->ctlr_mutex);
+	mutex_lock(&ctlr_dev->lock);
+
+	fcf = fcoe_fcf_device_priv(fcf_dev);
+	if (fcf)
+		fcf_dev->selected = (fcf == fip->sel_fcf) ? 1 : 0;
+	else
+		fcf_dev->selected = 0;
+
+	mutex_unlock(&ctlr_dev->lock);
+	mutex_unlock(&fip->ctlr_mutex);
+}
+EXPORT_SYMBOL(fcoe_fcf_get_selected);
+
+void fcoe_ctlr_set_fip_mode(struct fcoe_ctlr_device *ctlr_dev)
+{
+	struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev);
+	struct fc_lport *lport = ctlr->lp;
+
+	mutex_lock(&ctlr->ctlr_mutex);
+	switch (ctlr_dev->mode) {
+	case FIP_CONN_TYPE_VN2VN:
+		ctlr->mode = FIP_MODE_VN2VN;
+		break;
+	case FIP_CONN_TYPE_FABRIC:
+	default:
+		ctlr->mode = FIP_MODE_FABRIC;
+		break;
+	}
+
+	mutex_unlock(&ctlr->ctlr_mutex);
+
+	fcoe_ctlr_mode_set(lport, ctlr, ctlr->mode);
+}
+EXPORT_SYMBOL(fcoe_ctlr_set_fip_mode);
diff --git a/drivers/scsi/fcoe/fcoe_sysfs.c b/drivers/scsi/fcoe/fcoe_sysfs.c
new file mode 100644
index 0000000..5c8310b
--- /dev/null
+++ b/drivers/scsi/fcoe/fcoe_sysfs.c
@@ -0,0 +1,1072 @@
+/*
+ * Copyright(c) 2011 - 2012 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Maintained at www.Open-FCoE.org
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/etherdevice.h>
+#include <linux/ctype.h>
+
+#include <scsi/fcoe_sysfs.h>
+#include <scsi/libfcoe.h>
+
+/*
+ * OK to include local libfcoe.h for debug_logging, but cannot include
+ * <scsi/libfcoe.h> otherwise non-netdev based fcoe solutions would have
+ * have to include more than fcoe_sysfs.h.
+ */
+#include "libfcoe.h"
+
+static atomic_t ctlr_num;
+static atomic_t fcf_num;
+
+/*
+ * fcoe_fcf_dev_loss_tmo: the default number of seconds that fcoe sysfs
+ * should insulate the loss of a fcf.
+ */
+static unsigned int fcoe_fcf_dev_loss_tmo = 1800;  /* seconds */
+
+module_param_named(fcf_dev_loss_tmo, fcoe_fcf_dev_loss_tmo,
+		   uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(fcf_dev_loss_tmo,
+		 "Maximum number of seconds that libfcoe should"
+		 " insulate the loss of a fcf. Once this value is"
+		 " exceeded, the fcf is removed.");
+
+/*
+ * These are used by the fcoe_*_show_function routines, they
+ * are intentionally placed in the .c file as they're not intended
+ * for use throughout the code.
+ */
+#define fcoe_ctlr_id(x)				\
+	((x)->id)
+#define fcoe_ctlr_work_q_name(x)		\
+	((x)->work_q_name)
+#define fcoe_ctlr_work_q(x)			\
+	((x)->work_q)
+#define fcoe_ctlr_devloss_work_q_name(x)	\
+	((x)->devloss_work_q_name)
+#define fcoe_ctlr_devloss_work_q(x)		\
+	((x)->devloss_work_q)
+#define fcoe_ctlr_mode(x)			\
+	((x)->mode)
+#define fcoe_ctlr_fcf_dev_loss_tmo(x)		\
+	((x)->fcf_dev_loss_tmo)
+#define fcoe_ctlr_link_fail(x)			\
+	((x)->lesb.lesb_link_fail)
+#define fcoe_ctlr_vlink_fail(x)			\
+	((x)->lesb.lesb_vlink_fail)
+#define fcoe_ctlr_miss_fka(x)			\
+	((x)->lesb.lesb_miss_fka)
+#define fcoe_ctlr_symb_err(x)			\
+	((x)->lesb.lesb_symb_err)
+#define fcoe_ctlr_err_block(x)			\
+	((x)->lesb.lesb_err_block)
+#define fcoe_ctlr_fcs_error(x)			\
+	((x)->lesb.lesb_fcs_error)
+#define fcoe_ctlr_enabled(x)			\
+	((x)->enabled)
+#define fcoe_fcf_state(x)			\
+	((x)->state)
+#define fcoe_fcf_fabric_name(x)			\
+	((x)->fabric_name)
+#define fcoe_fcf_switch_name(x)			\
+	((x)->switch_name)
+#define fcoe_fcf_fc_map(x)			\
+	((x)->fc_map)
+#define fcoe_fcf_vfid(x)			\
+	((x)->vfid)
+#define fcoe_fcf_mac(x)				\
+	((x)->mac)
+#define fcoe_fcf_priority(x)			\
+	((x)->priority)
+#define fcoe_fcf_fka_period(x)			\
+	((x)->fka_period)
+#define fcoe_fcf_dev_loss_tmo(x)		\
+	((x)->dev_loss_tmo)
+#define fcoe_fcf_selected(x)			\
+	((x)->selected)
+#define fcoe_fcf_vlan_id(x)			\
+	((x)->vlan_id)
+
+/*
+ * dev_loss_tmo attribute
+ */
+static int fcoe_str_to_dev_loss(const char *buf, unsigned long *val)
+{
+	int ret;
+
+	ret = kstrtoul(buf, 0, val);
+	if (ret)
+		return -EINVAL;
+	/*
+	 * Check for overflow; dev_loss_tmo is u32
+	 */
+	if (*val > UINT_MAX)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int fcoe_fcf_set_dev_loss_tmo(struct fcoe_fcf_device *fcf,
+				     unsigned long val)
+{
+	if ((fcf->state == FCOE_FCF_STATE_UNKNOWN) ||
+	    (fcf->state == FCOE_FCF_STATE_DISCONNECTED) ||
+	    (fcf->state == FCOE_FCF_STATE_DELETED))
+		return -EBUSY;
+	/*
+	 * Check for overflow; dev_loss_tmo is u32
+	 */
+	if (val > UINT_MAX)
+		return -EINVAL;
+
+	fcoe_fcf_dev_loss_tmo(fcf) = val;
+	return 0;
+}
+
+#define FCOE_DEVICE_ATTR(_prefix, _name, _mode, _show, _store)	\
+struct device_attribute device_attr_fcoe_##_prefix##_##_name =	\
+	__ATTR(_name, _mode, _show, _store)
+
+#define fcoe_ctlr_show_function(field, format_string, sz, cast)	\
+static ssize_t show_fcoe_ctlr_device_##field(struct device *dev, \
+					    struct device_attribute *attr, \
+					    char *buf)			\
+{									\
+	struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev);		\
+	if (ctlr->f->get_fcoe_ctlr_##field)				\
+		ctlr->f->get_fcoe_ctlr_##field(ctlr);			\
+	return snprintf(buf, sz, format_string,				\
+			cast fcoe_ctlr_##field(ctlr));			\
+}
+
+#define fcoe_fcf_show_function(field, format_string, sz, cast)	\
+static ssize_t show_fcoe_fcf_device_##field(struct device *dev,	\
+					   struct device_attribute *attr, \
+					   char *buf)			\
+{									\
+	struct fcoe_fcf_device *fcf = dev_to_fcf(dev);			\
+	struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf);	\
+	if (ctlr->f->get_fcoe_fcf_##field)				\
+		ctlr->f->get_fcoe_fcf_##field(fcf);			\
+	return snprintf(buf, sz, format_string,				\
+			cast fcoe_fcf_##field(fcf));			\
+}
+
+#define fcoe_ctlr_private_show_function(field, format_string, sz, cast)	\
+static ssize_t show_fcoe_ctlr_device_##field(struct device *dev, \
+					    struct device_attribute *attr, \
+					    char *buf)			\
+{									\
+	struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev);		\
+	return snprintf(buf, sz, format_string, cast fcoe_ctlr_##field(ctlr)); \
+}
+
+#define fcoe_fcf_private_show_function(field, format_string, sz, cast)	\
+static ssize_t show_fcoe_fcf_device_##field(struct device *dev,	\
+					   struct device_attribute *attr, \
+					   char *buf)			\
+{								\
+	struct fcoe_fcf_device *fcf = dev_to_fcf(dev);			\
+	return snprintf(buf, sz, format_string, cast fcoe_fcf_##field(fcf)); \
+}
+
+#define fcoe_ctlr_private_rd_attr(field, format_string, sz)		\
+	fcoe_ctlr_private_show_function(field, format_string, sz, )	\
+	static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO,			\
+				show_fcoe_ctlr_device_##field, NULL)
+
+#define fcoe_ctlr_rd_attr(field, format_string, sz)			\
+	fcoe_ctlr_show_function(field, format_string, sz, )		\
+	static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO,			\
+				show_fcoe_ctlr_device_##field, NULL)
+
+#define fcoe_fcf_rd_attr(field, format_string, sz)			\
+	fcoe_fcf_show_function(field, format_string, sz, )		\
+	static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO,			\
+				show_fcoe_fcf_device_##field, NULL)
+
+#define fcoe_fcf_private_rd_attr(field, format_string, sz)		\
+	fcoe_fcf_private_show_function(field, format_string, sz, )	\
+	static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO,			\
+				show_fcoe_fcf_device_##field, NULL)
+
+#define fcoe_ctlr_private_rd_attr_cast(field, format_string, sz, cast)	\
+	fcoe_ctlr_private_show_function(field, format_string, sz, (cast)) \
+	static FCOE_DEVICE_ATTR(ctlr, field, S_IRUGO,			\
+				show_fcoe_ctlr_device_##field, NULL)
+
+#define fcoe_fcf_private_rd_attr_cast(field, format_string, sz, cast)	\
+	fcoe_fcf_private_show_function(field, format_string, sz, (cast)) \
+	static FCOE_DEVICE_ATTR(fcf, field, S_IRUGO,			\
+				show_fcoe_fcf_device_##field, NULL)
+
+#define fcoe_enum_name_search(title, table_type, table)			\
+static const char *get_fcoe_##title##_name(enum table_type table_key)	\
+{									\
+	if (table_key < 0 || table_key >= ARRAY_SIZE(table))		\
+		return NULL;						\
+	return table[table_key];					\
+}
+
+static char *fip_conn_type_names[] = {
+	[ FIP_CONN_TYPE_UNKNOWN ] = "Unknown",
+	[ FIP_CONN_TYPE_FABRIC ]  = "Fabric",
+	[ FIP_CONN_TYPE_VN2VN ]   = "VN2VN",
+};
+fcoe_enum_name_search(ctlr_mode, fip_conn_type, fip_conn_type_names)
+
+static enum fip_conn_type fcoe_parse_mode(const char *buf)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(fip_conn_type_names); i++) {
+		if (strcasecmp(buf, fip_conn_type_names[i]) == 0)
+			return i;
+	}
+
+	return FIP_CONN_TYPE_UNKNOWN;
+}
+
+static char *fcf_state_names[] = {
+	[ FCOE_FCF_STATE_UNKNOWN ]      = "Unknown",
+	[ FCOE_FCF_STATE_DISCONNECTED ] = "Disconnected",
+	[ FCOE_FCF_STATE_CONNECTED ]    = "Connected",
+};
+fcoe_enum_name_search(fcf_state, fcf_state, fcf_state_names)
+#define FCOE_FCF_STATE_MAX_NAMELEN 50
+
+static ssize_t show_fcf_state(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct fcoe_fcf_device *fcf = dev_to_fcf(dev);
+	const char *name;
+	name = get_fcoe_fcf_state_name(fcf->state);
+	if (!name)
+		return -EINVAL;
+	return snprintf(buf, FCOE_FCF_STATE_MAX_NAMELEN, "%s\n", name);
+}
+static FCOE_DEVICE_ATTR(fcf, state, S_IRUGO, show_fcf_state, NULL);
+
+#define FCOE_MAX_MODENAME_LEN 20
+static ssize_t show_ctlr_mode(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev);
+	const char *name;
+
+	name = get_fcoe_ctlr_mode_name(ctlr->mode);
+	if (!name)
+		return -EINVAL;
+	return snprintf(buf, FCOE_MAX_MODENAME_LEN,
+			"%s\n", name);
+}
+
+static ssize_t store_ctlr_mode(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev);
+	char mode[FCOE_MAX_MODENAME_LEN + 1];
+
+	if (count > FCOE_MAX_MODENAME_LEN)
+		return -EINVAL;
+
+	strncpy(mode, buf, count);
+
+	if (mode[count - 1] == '\n')
+		mode[count - 1] = '\0';
+	else
+		mode[count] = '\0';
+
+	switch (ctlr->enabled) {
+	case FCOE_CTLR_ENABLED:
+		LIBFCOE_SYSFS_DBG(ctlr, "Cannot change mode when enabled.\n");
+		return -EBUSY;
+	case FCOE_CTLR_DISABLED:
+		if (!ctlr->f->set_fcoe_ctlr_mode) {
+			LIBFCOE_SYSFS_DBG(ctlr,
+					  "Mode change not supported by LLD.\n");
+			return -ENOTSUPP;
+		}
+
+		ctlr->mode = fcoe_parse_mode(mode);
+		if (ctlr->mode == FIP_CONN_TYPE_UNKNOWN) {
+			LIBFCOE_SYSFS_DBG(ctlr, "Unknown mode %s provided.\n",
+					  buf);
+			return -EINVAL;
+		}
+
+		ctlr->f->set_fcoe_ctlr_mode(ctlr);
+		LIBFCOE_SYSFS_DBG(ctlr, "Mode changed to %s.\n", buf);
+
+		return count;
+	case FCOE_CTLR_UNUSED:
+	default:
+		LIBFCOE_SYSFS_DBG(ctlr, "Mode change not supported.\n");
+		return -ENOTSUPP;
+	};
+}
+
+static FCOE_DEVICE_ATTR(ctlr, mode, S_IRUGO | S_IWUSR,
+			show_ctlr_mode, store_ctlr_mode);
+
+static ssize_t store_ctlr_enabled(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev);
+	bool enabled;
+	int rc;
+
+	if (*buf == '1')
+		enabled = true;
+	else if (*buf == '0')
+		enabled = false;
+	else
+		return -EINVAL;
+
+	switch (ctlr->enabled) {
+	case FCOE_CTLR_ENABLED:
+		if (enabled)
+			return count;
+		ctlr->enabled = FCOE_CTLR_DISABLED;
+		break;
+	case FCOE_CTLR_DISABLED:
+		if (!enabled)
+			return count;
+		ctlr->enabled = FCOE_CTLR_ENABLED;
+		break;
+	case FCOE_CTLR_UNUSED:
+		return -ENOTSUPP;
+	};
+
+	rc = ctlr->f->set_fcoe_ctlr_enabled(ctlr);
+	if (rc)
+		return rc;
+
+	return count;
+}
+
+static char *ctlr_enabled_state_names[] = {
+	[ FCOE_CTLR_ENABLED ]  = "1",
+	[ FCOE_CTLR_DISABLED ] = "0",
+};
+fcoe_enum_name_search(ctlr_enabled_state, ctlr_enabled_state,
+		      ctlr_enabled_state_names)
+#define FCOE_CTLR_ENABLED_MAX_NAMELEN 50
+
+static ssize_t show_ctlr_enabled_state(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev);
+	const char *name;
+
+	name = get_fcoe_ctlr_enabled_state_name(ctlr->enabled);
+	if (!name)
+		return -EINVAL;
+	return snprintf(buf, FCOE_CTLR_ENABLED_MAX_NAMELEN,
+			"%s\n", name);
+}
+
+static FCOE_DEVICE_ATTR(ctlr, enabled, S_IRUGO | S_IWUSR,
+			show_ctlr_enabled_state,
+			store_ctlr_enabled);
+
+static ssize_t store_ctlr_fip_resp(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev);
+	struct fcoe_ctlr *fip = fcoe_ctlr_device_priv(ctlr);
+
+	mutex_lock(&fip->ctlr_mutex);
+	if ((buf[1] == '\0') || ((buf[1] == '\n') && (buf[2] == '\0'))) {
+		if (buf[0] == '1') {
+			fip->fip_resp = 1;
+			mutex_unlock(&fip->ctlr_mutex);
+			return count;
+		}
+		if (buf[0] == '0') {
+			fip->fip_resp = 0;
+			mutex_unlock(&fip->ctlr_mutex);
+			return count;
+		}
+	}
+	mutex_unlock(&fip->ctlr_mutex);
+	return -EINVAL;
+}
+
+static ssize_t show_ctlr_fip_resp(struct device *dev,
+				  struct device_attribute *attr,
+				  char *buf)
+{
+	struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev);
+	struct fcoe_ctlr *fip = fcoe_ctlr_device_priv(ctlr);
+
+	return sprintf(buf, "%d\n", fip->fip_resp ? 1 : 0);
+}
+
+static FCOE_DEVICE_ATTR(ctlr, fip_vlan_responder, S_IRUGO | S_IWUSR,
+			show_ctlr_fip_resp,
+			store_ctlr_fip_resp);
+
+static ssize_t
+fcoe_ctlr_var_store(u32 *var, const char *buf, size_t count)
+{
+	int err;
+	unsigned long v;
+
+	err = kstrtoul(buf, 10, &v);
+	if (err || v > UINT_MAX)
+		return -EINVAL;
+
+	*var = v;
+
+	return count;
+}
+
+static ssize_t store_ctlr_r_a_tov(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct fcoe_ctlr_device *ctlr_dev = dev_to_ctlr(dev);
+	struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev);
+
+	if (ctlr_dev->enabled == FCOE_CTLR_ENABLED)
+		return -EBUSY;
+	if (ctlr_dev->enabled == FCOE_CTLR_DISABLED)
+		return fcoe_ctlr_var_store(&ctlr->lp->r_a_tov, buf, count);
+	return -ENOTSUPP;
+}
+
+static ssize_t show_ctlr_r_a_tov(struct device *dev,
+				 struct device_attribute *attr,
+				 char *buf)
+{
+	struct fcoe_ctlr_device *ctlr_dev = dev_to_ctlr(dev);
+	struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev);
+
+	return sprintf(buf, "%d\n", ctlr->lp->r_a_tov);
+}
+
+static FCOE_DEVICE_ATTR(ctlr, r_a_tov, S_IRUGO | S_IWUSR,
+			show_ctlr_r_a_tov, store_ctlr_r_a_tov);
+
+static ssize_t store_ctlr_e_d_tov(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct fcoe_ctlr_device *ctlr_dev = dev_to_ctlr(dev);
+	struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev);
+
+	if (ctlr_dev->enabled == FCOE_CTLR_ENABLED)
+		return -EBUSY;
+	if (ctlr_dev->enabled == FCOE_CTLR_DISABLED)
+		return fcoe_ctlr_var_store(&ctlr->lp->e_d_tov, buf, count);
+	return -ENOTSUPP;
+}
+
+static ssize_t show_ctlr_e_d_tov(struct device *dev,
+				 struct device_attribute *attr,
+				 char *buf)
+{
+	struct fcoe_ctlr_device *ctlr_dev = dev_to_ctlr(dev);
+	struct fcoe_ctlr *ctlr = fcoe_ctlr_device_priv(ctlr_dev);
+
+	return sprintf(buf, "%d\n", ctlr->lp->e_d_tov);
+}
+
+static FCOE_DEVICE_ATTR(ctlr, e_d_tov, S_IRUGO | S_IWUSR,
+			show_ctlr_e_d_tov, store_ctlr_e_d_tov);
+
+static ssize_t
+store_private_fcoe_ctlr_fcf_dev_loss_tmo(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev);
+	struct fcoe_fcf_device *fcf;
+	unsigned long val;
+	int rc;
+
+	rc = fcoe_str_to_dev_loss(buf, &val);
+	if (rc)
+		return rc;
+
+	fcoe_ctlr_fcf_dev_loss_tmo(ctlr) = val;
+	mutex_lock(&ctlr->lock);
+	list_for_each_entry(fcf, &ctlr->fcfs, peers)
+		fcoe_fcf_set_dev_loss_tmo(fcf, val);
+	mutex_unlock(&ctlr->lock);
+	return count;
+}
+fcoe_ctlr_private_show_function(fcf_dev_loss_tmo, "%d\n", 20, );
+static FCOE_DEVICE_ATTR(ctlr, fcf_dev_loss_tmo, S_IRUGO | S_IWUSR,
+			show_fcoe_ctlr_device_fcf_dev_loss_tmo,
+			store_private_fcoe_ctlr_fcf_dev_loss_tmo);
+
+/* Link Error Status Block (LESB) */
+fcoe_ctlr_rd_attr(link_fail, "%u\n", 20);
+fcoe_ctlr_rd_attr(vlink_fail, "%u\n", 20);
+fcoe_ctlr_rd_attr(miss_fka, "%u\n", 20);
+fcoe_ctlr_rd_attr(symb_err, "%u\n", 20);
+fcoe_ctlr_rd_attr(err_block, "%u\n", 20);
+fcoe_ctlr_rd_attr(fcs_error, "%u\n", 20);
+
+fcoe_fcf_private_rd_attr_cast(fabric_name, "0x%llx\n", 20, unsigned long long);
+fcoe_fcf_private_rd_attr_cast(switch_name, "0x%llx\n", 20, unsigned long long);
+fcoe_fcf_private_rd_attr(priority, "%u\n", 20);
+fcoe_fcf_private_rd_attr(fc_map, "0x%x\n", 20);
+fcoe_fcf_private_rd_attr(vfid, "%u\n", 20);
+fcoe_fcf_private_rd_attr(mac, "%pM\n", 20);
+fcoe_fcf_private_rd_attr(fka_period, "%u\n", 20);
+fcoe_fcf_rd_attr(selected, "%u\n", 20);
+fcoe_fcf_rd_attr(vlan_id, "%u\n", 20);
+
+fcoe_fcf_private_show_function(dev_loss_tmo, "%d\n", 20, )
+static ssize_t
+store_fcoe_fcf_dev_loss_tmo(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	struct fcoe_fcf_device *fcf = dev_to_fcf(dev);
+	unsigned long val;
+	int rc;
+
+	rc = fcoe_str_to_dev_loss(buf, &val);
+	if (rc)
+		return rc;
+
+	rc = fcoe_fcf_set_dev_loss_tmo(fcf, val);
+	if (rc)
+		return rc;
+	return count;
+}
+static FCOE_DEVICE_ATTR(fcf, dev_loss_tmo, S_IRUGO | S_IWUSR,
+			show_fcoe_fcf_device_dev_loss_tmo,
+			store_fcoe_fcf_dev_loss_tmo);
+
+static struct attribute *fcoe_ctlr_lesb_attrs[] = {
+	&device_attr_fcoe_ctlr_link_fail.attr,
+	&device_attr_fcoe_ctlr_vlink_fail.attr,
+	&device_attr_fcoe_ctlr_miss_fka.attr,
+	&device_attr_fcoe_ctlr_symb_err.attr,
+	&device_attr_fcoe_ctlr_err_block.attr,
+	&device_attr_fcoe_ctlr_fcs_error.attr,
+	NULL,
+};
+
+static struct attribute_group fcoe_ctlr_lesb_attr_group = {
+	.name = "lesb",
+	.attrs = fcoe_ctlr_lesb_attrs,
+};
+
+static struct attribute *fcoe_ctlr_attrs[] = {
+	&device_attr_fcoe_ctlr_fip_vlan_responder.attr,
+	&device_attr_fcoe_ctlr_fcf_dev_loss_tmo.attr,
+	&device_attr_fcoe_ctlr_r_a_tov.attr,
+	&device_attr_fcoe_ctlr_e_d_tov.attr,
+	&device_attr_fcoe_ctlr_enabled.attr,
+	&device_attr_fcoe_ctlr_mode.attr,
+	NULL,
+};
+
+static struct attribute_group fcoe_ctlr_attr_group = {
+	.attrs = fcoe_ctlr_attrs,
+};
+
+static const struct attribute_group *fcoe_ctlr_attr_groups[] = {
+	&fcoe_ctlr_attr_group,
+	&fcoe_ctlr_lesb_attr_group,
+	NULL,
+};
+
+static struct attribute *fcoe_fcf_attrs[] = {
+	&device_attr_fcoe_fcf_fabric_name.attr,
+	&device_attr_fcoe_fcf_switch_name.attr,
+	&device_attr_fcoe_fcf_dev_loss_tmo.attr,
+	&device_attr_fcoe_fcf_fc_map.attr,
+	&device_attr_fcoe_fcf_vfid.attr,
+	&device_attr_fcoe_fcf_mac.attr,
+	&device_attr_fcoe_fcf_priority.attr,
+	&device_attr_fcoe_fcf_fka_period.attr,
+	&device_attr_fcoe_fcf_state.attr,
+	&device_attr_fcoe_fcf_selected.attr,
+	&device_attr_fcoe_fcf_vlan_id.attr,
+	NULL
+};
+
+static struct attribute_group fcoe_fcf_attr_group = {
+	.attrs = fcoe_fcf_attrs,
+};
+
+static const struct attribute_group *fcoe_fcf_attr_groups[] = {
+	&fcoe_fcf_attr_group,
+	NULL,
+};
+
+static struct bus_type fcoe_bus_type;
+
+static int fcoe_bus_match(struct device *dev,
+			  struct device_driver *drv)
+{
+	if (dev->bus == &fcoe_bus_type)
+		return 1;
+	return 0;
+}
+
+/**
+ * fcoe_ctlr_device_release() - Release the FIP ctlr memory
+ * @dev: Pointer to the FIP ctlr's embedded device
+ *
+ * Called when the last FIP ctlr reference is released.
+ */
+static void fcoe_ctlr_device_release(struct device *dev)
+{
+	struct fcoe_ctlr_device *ctlr = dev_to_ctlr(dev);
+	kfree(ctlr);
+}
+
+/**
+ * fcoe_fcf_device_release() - Release the FIP fcf memory
+ * @dev: Pointer to the fcf's embedded device
+ *
+ * Called when the last FIP fcf reference is released.
+ */
+static void fcoe_fcf_device_release(struct device *dev)
+{
+	struct fcoe_fcf_device *fcf = dev_to_fcf(dev);
+	kfree(fcf);
+}
+
+static const struct device_type fcoe_ctlr_device_type = {
+	.name = "fcoe_ctlr",
+	.groups = fcoe_ctlr_attr_groups,
+	.release = fcoe_ctlr_device_release,
+};
+
+static const struct device_type fcoe_fcf_device_type = {
+	.name = "fcoe_fcf",
+	.groups = fcoe_fcf_attr_groups,
+	.release = fcoe_fcf_device_release,
+};
+
+static BUS_ATTR(ctlr_create, S_IWUSR, NULL, fcoe_ctlr_create_store);
+static BUS_ATTR(ctlr_destroy, S_IWUSR, NULL, fcoe_ctlr_destroy_store);
+
+static struct attribute *fcoe_bus_attrs[] = {
+	&bus_attr_ctlr_create.attr,
+	&bus_attr_ctlr_destroy.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(fcoe_bus);
+
+static struct bus_type fcoe_bus_type = {
+	.name = "fcoe",
+	.match = &fcoe_bus_match,
+	.bus_groups = fcoe_bus_groups,
+};
+
+/**
+ * fcoe_ctlr_device_flush_work() - Flush a FIP ctlr's workqueue
+ * @ctlr: Pointer to the FIP ctlr whose workqueue is to be flushed
+ */
+static void fcoe_ctlr_device_flush_work(struct fcoe_ctlr_device *ctlr)
+{
+	if (!fcoe_ctlr_work_q(ctlr)) {
+		printk(KERN_ERR
+		       "ERROR: FIP Ctlr '%d' attempted to flush work, "
+		       "when no workqueue created.\n", ctlr->id);
+		dump_stack();
+		return;
+	}
+
+	flush_workqueue(fcoe_ctlr_work_q(ctlr));
+}
+
+/**
+ * fcoe_ctlr_device_queue_work() - Schedule work for a FIP ctlr's workqueue
+ * @ctlr: Pointer to the FIP ctlr who owns the devloss workqueue
+ * @work:   Work to queue for execution
+ *
+ * Return value:
+ *	1 on success / 0 already queued / < 0 for error
+ */
+static int fcoe_ctlr_device_queue_work(struct fcoe_ctlr_device *ctlr,
+				       struct work_struct *work)
+{
+	if (unlikely(!fcoe_ctlr_work_q(ctlr))) {
+		printk(KERN_ERR
+		       "ERROR: FIP Ctlr '%d' attempted to queue work, "
+		       "when no workqueue created.\n", ctlr->id);
+		dump_stack();
+
+		return -EINVAL;
+	}
+
+	return queue_work(fcoe_ctlr_work_q(ctlr), work);
+}
+
+/**
+ * fcoe_ctlr_device_flush_devloss() - Flush a FIP ctlr's devloss workqueue
+ * @ctlr: Pointer to FIP ctlr whose workqueue is to be flushed
+ */
+static void fcoe_ctlr_device_flush_devloss(struct fcoe_ctlr_device *ctlr)
+{
+	if (!fcoe_ctlr_devloss_work_q(ctlr)) {
+		printk(KERN_ERR
+		       "ERROR: FIP Ctlr '%d' attempted to flush work, "
+		       "when no workqueue created.\n", ctlr->id);
+		dump_stack();
+		return;
+	}
+
+	flush_workqueue(fcoe_ctlr_devloss_work_q(ctlr));
+}
+
+/**
+ * fcoe_ctlr_device_queue_devloss_work() - Schedule work for a FIP ctlr's devloss workqueue
+ * @ctlr: Pointer to the FIP ctlr who owns the devloss workqueue
+ * @work:   Work to queue for execution
+ * @delay:  jiffies to delay the work queuing
+ *
+ * Return value:
+ *	1 on success / 0 already queued / < 0 for error
+ */
+static int fcoe_ctlr_device_queue_devloss_work(struct fcoe_ctlr_device *ctlr,
+					       struct delayed_work *work,
+					       unsigned long delay)
+{
+	if (unlikely(!fcoe_ctlr_devloss_work_q(ctlr))) {
+		printk(KERN_ERR
+		       "ERROR: FIP Ctlr '%d' attempted to queue work, "
+		       "when no workqueue created.\n", ctlr->id);
+		dump_stack();
+
+		return -EINVAL;
+	}
+
+	return queue_delayed_work(fcoe_ctlr_devloss_work_q(ctlr), work, delay);
+}
+
+static int fcoe_fcf_device_match(struct fcoe_fcf_device *new,
+				 struct fcoe_fcf_device *old)
+{
+	if (new->switch_name == old->switch_name &&
+	    new->fabric_name == old->fabric_name &&
+	    new->fc_map == old->fc_map &&
+	    ether_addr_equal(new->mac, old->mac))
+		return 1;
+	return 0;
+}
+
+/**
+ * fcoe_ctlr_device_add() - Add a FIP ctlr to sysfs
+ * @parent:    The parent device to which the fcoe_ctlr instance
+ *             should be attached
+ * @f:         The LLD's FCoE sysfs function template pointer
+ * @priv_size: Size to be allocated with the fcoe_ctlr_device for the LLD
+ *
+ * This routine allocates a FIP ctlr object with some additional memory
+ * for the LLD. The FIP ctlr is initialized, added to sysfs and then
+ * attributes are added to it.
+ */
+struct fcoe_ctlr_device *fcoe_ctlr_device_add(struct device *parent,
+				    struct fcoe_sysfs_function_template *f,
+				    int priv_size)
+{
+	struct fcoe_ctlr_device *ctlr;
+	int error = 0;
+
+	ctlr = kzalloc(sizeof(struct fcoe_ctlr_device) + priv_size,
+		       GFP_KERNEL);
+	if (!ctlr)
+		goto out;
+
+	ctlr->id = atomic_inc_return(&ctlr_num) - 1;
+	ctlr->f = f;
+	ctlr->mode = FIP_CONN_TYPE_FABRIC;
+	INIT_LIST_HEAD(&ctlr->fcfs);
+	mutex_init(&ctlr->lock);
+	ctlr->dev.parent = parent;
+	ctlr->dev.bus = &fcoe_bus_type;
+	ctlr->dev.type = &fcoe_ctlr_device_type;
+
+	ctlr->fcf_dev_loss_tmo = fcoe_fcf_dev_loss_tmo;
+
+	snprintf(ctlr->work_q_name, sizeof(ctlr->work_q_name),
+		 "ctlr_wq_%d", ctlr->id);
+	ctlr->work_q = create_singlethread_workqueue(
+		ctlr->work_q_name);
+	if (!ctlr->work_q)
+		goto out_del;
+
+	snprintf(ctlr->devloss_work_q_name,
+		 sizeof(ctlr->devloss_work_q_name),
+		 "ctlr_dl_wq_%d", ctlr->id);
+	ctlr->devloss_work_q = create_singlethread_workqueue(
+		ctlr->devloss_work_q_name);
+	if (!ctlr->devloss_work_q)
+		goto out_del_q;
+
+	dev_set_name(&ctlr->dev, "ctlr_%d", ctlr->id);
+	error = device_register(&ctlr->dev);
+	if (error)
+		goto out_del_q2;
+
+	return ctlr;
+
+out_del_q2:
+	destroy_workqueue(ctlr->devloss_work_q);
+	ctlr->devloss_work_q = NULL;
+out_del_q:
+	destroy_workqueue(ctlr->work_q);
+	ctlr->work_q = NULL;
+out_del:
+	kfree(ctlr);
+out:
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(fcoe_ctlr_device_add);
+
+/**
+ * fcoe_ctlr_device_delete() - Delete a FIP ctlr and its subtree from sysfs
+ * @ctlr: A pointer to the ctlr to be deleted
+ *
+ * Deletes a FIP ctlr and any fcfs attached
+ * to it. Deleting fcfs will cause their childen
+ * to be deleted as well.
+ *
+ * The ctlr is detached from sysfs and it's resources
+ * are freed (work q), but the memory is not freed
+ * until its last reference is released.
+ *
+ * This routine expects no locks to be held before
+ * calling.
+ *
+ * TODO: Currently there are no callbacks to clean up LLD data
+ * for a fcoe_fcf_device. LLDs must keep this in mind as they need
+ * to clean up each of their LLD data for all fcoe_fcf_device before
+ * calling fcoe_ctlr_device_delete.
+ */
+void fcoe_ctlr_device_delete(struct fcoe_ctlr_device *ctlr)
+{
+	struct fcoe_fcf_device *fcf, *next;
+	/* Remove any attached fcfs */
+	mutex_lock(&ctlr->lock);
+	list_for_each_entry_safe(fcf, next,
+				 &ctlr->fcfs, peers) {
+		list_del(&fcf->peers);
+		fcf->state = FCOE_FCF_STATE_DELETED;
+		fcoe_ctlr_device_queue_work(ctlr, &fcf->delete_work);
+	}
+	mutex_unlock(&ctlr->lock);
+
+	fcoe_ctlr_device_flush_work(ctlr);
+
+	destroy_workqueue(ctlr->devloss_work_q);
+	ctlr->devloss_work_q = NULL;
+	destroy_workqueue(ctlr->work_q);
+	ctlr->work_q = NULL;
+
+	device_unregister(&ctlr->dev);
+}
+EXPORT_SYMBOL_GPL(fcoe_ctlr_device_delete);
+
+/**
+ * fcoe_fcf_device_final_delete() - Final delete routine
+ * @work: The FIP fcf's embedded work struct
+ *
+ * It is expected that the fcf has been removed from
+ * the FIP ctlr's list before calling this routine.
+ */
+static void fcoe_fcf_device_final_delete(struct work_struct *work)
+{
+	struct fcoe_fcf_device *fcf =
+		container_of(work, struct fcoe_fcf_device, delete_work);
+	struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf);
+
+	/*
+	 * Cancel any outstanding timers. These should really exist
+	 * only when rmmod'ing the LLDD and we're asking for
+	 * immediate termination of the rports
+	 */
+	if (!cancel_delayed_work(&fcf->dev_loss_work))
+		fcoe_ctlr_device_flush_devloss(ctlr);
+
+	device_unregister(&fcf->dev);
+}
+
+/**
+ * fip_timeout_deleted_fcf() - Delete a fcf when the devloss timer fires
+ * @work: The FIP fcf's embedded work struct
+ *
+ * Removes the fcf from the FIP ctlr's list of fcfs and
+ * queues the final deletion.
+ */
+static void fip_timeout_deleted_fcf(struct work_struct *work)
+{
+	struct fcoe_fcf_device *fcf =
+		container_of(work, struct fcoe_fcf_device, dev_loss_work.work);
+	struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf);
+
+	mutex_lock(&ctlr->lock);
+
+	/*
+	 * If the fcf is deleted or reconnected before the timer
+	 * fires the devloss queue will be flushed, but the state will
+	 * either be CONNECTED or DELETED. If that is the case we
+	 * cancel deleting the fcf.
+	 */
+	if (fcf->state != FCOE_FCF_STATE_DISCONNECTED)
+		goto out;
+
+	dev_printk(KERN_ERR, &fcf->dev,
+		   "FIP fcf connection time out: removing fcf\n");
+
+	list_del(&fcf->peers);
+	fcf->state = FCOE_FCF_STATE_DELETED;
+	fcoe_ctlr_device_queue_work(ctlr, &fcf->delete_work);
+
+out:
+	mutex_unlock(&ctlr->lock);
+}
+
+/**
+ * fcoe_fcf_device_delete() - Delete a FIP fcf
+ * @fcf: Pointer to the fcf which is to be deleted
+ *
+ * Queues the FIP fcf on the devloss workqueue
+ *
+ * Expects the ctlr_attrs mutex to be held for fcf
+ * state change.
+ */
+void fcoe_fcf_device_delete(struct fcoe_fcf_device *fcf)
+{
+	struct fcoe_ctlr_device *ctlr = fcoe_fcf_dev_to_ctlr_dev(fcf);
+	int timeout = fcf->dev_loss_tmo;
+
+	if (fcf->state != FCOE_FCF_STATE_CONNECTED)
+		return;
+
+	fcf->state = FCOE_FCF_STATE_DISCONNECTED;
+
+	/*
+	 * FCF will only be re-connected by the LLD calling
+	 * fcoe_fcf_device_add, and it should be setting up
+	 * priv then.
+	 */
+	fcf->priv = NULL;
+
+	fcoe_ctlr_device_queue_devloss_work(ctlr, &fcf->dev_loss_work,
+					   timeout * HZ);
+}
+EXPORT_SYMBOL_GPL(fcoe_fcf_device_delete);
+
+/**
+ * fcoe_fcf_device_add() - Add a FCoE sysfs fcoe_fcf_device to the system
+ * @ctlr:    The fcoe_ctlr_device that will be the fcoe_fcf_device parent
+ * @new_fcf: A temporary FCF used for lookups on the current list of fcfs
+ *
+ * Expects to be called with the ctlr->lock held
+ */
+struct fcoe_fcf_device *fcoe_fcf_device_add(struct fcoe_ctlr_device *ctlr,
+					    struct fcoe_fcf_device *new_fcf)
+{
+	struct fcoe_fcf_device *fcf;
+	int error = 0;
+
+	list_for_each_entry(fcf, &ctlr->fcfs, peers) {
+		if (fcoe_fcf_device_match(new_fcf, fcf)) {
+			if (fcf->state == FCOE_FCF_STATE_CONNECTED)
+				return fcf;
+
+			fcf->state = FCOE_FCF_STATE_CONNECTED;
+
+			if (!cancel_delayed_work(&fcf->dev_loss_work))
+				fcoe_ctlr_device_flush_devloss(ctlr);
+
+			return fcf;
+		}
+	}
+
+	fcf = kzalloc(sizeof(struct fcoe_fcf_device), GFP_ATOMIC);
+	if (unlikely(!fcf))
+		goto out;
+
+	INIT_WORK(&fcf->delete_work, fcoe_fcf_device_final_delete);
+	INIT_DELAYED_WORK(&fcf->dev_loss_work, fip_timeout_deleted_fcf);
+
+	fcf->dev.parent = &ctlr->dev;
+	fcf->dev.bus = &fcoe_bus_type;
+	fcf->dev.type = &fcoe_fcf_device_type;
+	fcf->id = atomic_inc_return(&fcf_num) - 1;
+	fcf->state = FCOE_FCF_STATE_UNKNOWN;
+
+	fcf->dev_loss_tmo = ctlr->fcf_dev_loss_tmo;
+
+	dev_set_name(&fcf->dev, "fcf_%d", fcf->id);
+
+	fcf->fabric_name = new_fcf->fabric_name;
+	fcf->switch_name = new_fcf->switch_name;
+	fcf->fc_map = new_fcf->fc_map;
+	fcf->vfid = new_fcf->vfid;
+	memcpy(fcf->mac, new_fcf->mac, ETH_ALEN);
+	fcf->priority = new_fcf->priority;
+	fcf->fka_period = new_fcf->fka_period;
+	fcf->selected = new_fcf->selected;
+
+	error = device_register(&fcf->dev);
+	if (error)
+		goto out_del;
+
+	fcf->state = FCOE_FCF_STATE_CONNECTED;
+	list_add_tail(&fcf->peers, &ctlr->fcfs);
+
+	return fcf;
+
+out_del:
+	kfree(fcf);
+out:
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(fcoe_fcf_device_add);
+
+int __init fcoe_sysfs_setup(void)
+{
+	int error;
+
+	atomic_set(&ctlr_num, 0);
+	atomic_set(&fcf_num, 0);
+
+	error = bus_register(&fcoe_bus_type);
+	if (error)
+		return error;
+
+	return 0;
+}
+
+void __exit fcoe_sysfs_teardown(void)
+{
+	bus_unregister(&fcoe_bus_type);
+}
diff --git a/drivers/scsi/fcoe/fcoe_transport.c b/drivers/scsi/fcoe/fcoe_transport.c
new file mode 100644
index 0000000..f4909cd
--- /dev/null
+++ b/drivers/scsi/fcoe/fcoe_transport.c
@@ -0,0 +1,1069 @@
+/*
+ * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Maintained at www.Open-FCoE.org
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/errno.h>
+#include <linux/crc32.h>
+#include <scsi/libfcoe.h>
+
+#include "libfcoe.h"
+
+MODULE_AUTHOR("Open-FCoE.org");
+MODULE_DESCRIPTION("FIP discovery protocol and FCoE transport for FCoE HBAs");
+MODULE_LICENSE("GPL v2");
+
+static int fcoe_transport_create(const char *, const struct kernel_param *);
+static int fcoe_transport_destroy(const char *, const struct kernel_param *);
+static int fcoe_transport_show(char *buffer, const struct kernel_param *kp);
+static struct fcoe_transport *fcoe_transport_lookup(struct net_device *device);
+static struct fcoe_transport *fcoe_netdev_map_lookup(struct net_device *device);
+static int fcoe_transport_enable(const char *, const struct kernel_param *);
+static int fcoe_transport_disable(const char *, const struct kernel_param *);
+static int libfcoe_device_notification(struct notifier_block *notifier,
+				    ulong event, void *ptr);
+
+static LIST_HEAD(fcoe_transports);
+static DEFINE_MUTEX(ft_mutex);
+static LIST_HEAD(fcoe_netdevs);
+static DEFINE_MUTEX(fn_mutex);
+
+unsigned int libfcoe_debug_logging;
+module_param_named(debug_logging, libfcoe_debug_logging, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels");
+
+module_param_call(show, NULL, fcoe_transport_show, NULL, S_IRUSR);
+__MODULE_PARM_TYPE(show, "string");
+MODULE_PARM_DESC(show, " Show attached FCoE transports");
+
+module_param_call(create, fcoe_transport_create, NULL,
+		  (void *)FIP_MODE_FABRIC, S_IWUSR);
+__MODULE_PARM_TYPE(create, "string");
+MODULE_PARM_DESC(create, " Creates fcoe instance on an ethernet interface");
+
+module_param_call(create_vn2vn, fcoe_transport_create, NULL,
+		  (void *)FIP_MODE_VN2VN, S_IWUSR);
+__MODULE_PARM_TYPE(create_vn2vn, "string");
+MODULE_PARM_DESC(create_vn2vn, " Creates a VN_node to VN_node FCoE instance "
+		 "on an Ethernet interface");
+
+module_param_call(destroy, fcoe_transport_destroy, NULL, NULL, S_IWUSR);
+__MODULE_PARM_TYPE(destroy, "string");
+MODULE_PARM_DESC(destroy, " Destroys fcoe instance on an ethernet interface");
+
+module_param_call(enable, fcoe_transport_enable, NULL, NULL, S_IWUSR);
+__MODULE_PARM_TYPE(enable, "string");
+MODULE_PARM_DESC(enable, " Enables fcoe on an ethernet interface.");
+
+module_param_call(disable, fcoe_transport_disable, NULL, NULL, S_IWUSR);
+__MODULE_PARM_TYPE(disable, "string");
+MODULE_PARM_DESC(disable, " Disables fcoe on an ethernet interface.");
+
+/* notification function for packets from net device */
+static struct notifier_block libfcoe_notifier = {
+	.notifier_call = libfcoe_device_notification,
+};
+
+static const struct {
+	u32 fc_port_speed;
+#define SPEED_2000	2000
+#define SPEED_4000	4000
+#define SPEED_8000	8000
+#define SPEED_16000	16000
+#define SPEED_32000	32000
+	u32 eth_port_speed;
+} fcoe_port_speed_mapping[] = {
+	{ FC_PORTSPEED_1GBIT,   SPEED_1000   },
+	{ FC_PORTSPEED_2GBIT,   SPEED_2000   },
+	{ FC_PORTSPEED_4GBIT,   SPEED_4000   },
+	{ FC_PORTSPEED_8GBIT,   SPEED_8000   },
+	{ FC_PORTSPEED_10GBIT,  SPEED_10000  },
+	{ FC_PORTSPEED_16GBIT,  SPEED_16000  },
+	{ FC_PORTSPEED_20GBIT,  SPEED_20000  },
+	{ FC_PORTSPEED_25GBIT,  SPEED_25000  },
+	{ FC_PORTSPEED_32GBIT,  SPEED_32000  },
+	{ FC_PORTSPEED_40GBIT,  SPEED_40000  },
+	{ FC_PORTSPEED_50GBIT,  SPEED_50000  },
+	{ FC_PORTSPEED_100GBIT, SPEED_100000 },
+};
+
+static inline u32 eth2fc_speed(u32 eth_port_speed)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(fcoe_port_speed_mapping); i++) {
+		if (fcoe_port_speed_mapping[i].eth_port_speed == eth_port_speed)
+			return fcoe_port_speed_mapping[i].fc_port_speed;
+	}
+
+	return FC_PORTSPEED_UNKNOWN;
+}
+
+/**
+ * fcoe_link_speed_update() - Update the supported and actual link speeds
+ * @lport: The local port to update speeds for
+ *
+ * Returns: 0 if the ethtool query was successful
+ *          -1 if the ethtool query failed
+ */
+int fcoe_link_speed_update(struct fc_lport *lport)
+{
+	struct net_device *netdev = fcoe_get_netdev(lport);
+	struct ethtool_link_ksettings ecmd;
+
+	if (!__ethtool_get_link_ksettings(netdev, &ecmd)) {
+		lport->link_supported_speeds &= ~(FC_PORTSPEED_1GBIT  |
+		                                  FC_PORTSPEED_10GBIT |
+		                                  FC_PORTSPEED_20GBIT |
+		                                  FC_PORTSPEED_40GBIT);
+
+		if (ecmd.link_modes.supported[0] & (
+			    SUPPORTED_1000baseT_Half |
+			    SUPPORTED_1000baseT_Full |
+			    SUPPORTED_1000baseKX_Full))
+			lport->link_supported_speeds |= FC_PORTSPEED_1GBIT;
+
+		if (ecmd.link_modes.supported[0] & (
+			    SUPPORTED_10000baseT_Full   |
+			    SUPPORTED_10000baseKX4_Full |
+			    SUPPORTED_10000baseKR_Full  |
+			    SUPPORTED_10000baseR_FEC))
+			lport->link_supported_speeds |= FC_PORTSPEED_10GBIT;
+
+		if (ecmd.link_modes.supported[0] & (
+			    SUPPORTED_20000baseMLD2_Full |
+			    SUPPORTED_20000baseKR2_Full))
+			lport->link_supported_speeds |= FC_PORTSPEED_20GBIT;
+
+		if (ecmd.link_modes.supported[0] & (
+			    SUPPORTED_40000baseKR4_Full |
+			    SUPPORTED_40000baseCR4_Full |
+			    SUPPORTED_40000baseSR4_Full |
+			    SUPPORTED_40000baseLR4_Full))
+			lport->link_supported_speeds |= FC_PORTSPEED_40GBIT;
+
+		lport->link_speed = eth2fc_speed(ecmd.base.speed);
+		return 0;
+	}
+	return -1;
+}
+EXPORT_SYMBOL_GPL(fcoe_link_speed_update);
+
+/**
+ * __fcoe_get_lesb() - Get the Link Error Status Block (LESB) for a given lport
+ * @lport: The local port to update speeds for
+ * @fc_lesb: Pointer to the LESB to be filled up
+ * @netdev: Pointer to the netdev that is associated with the lport
+ *
+ * Note, the Link Error Status Block (LESB) for FCoE is defined in FC-BB-6
+ * Clause 7.11 in v1.04.
+ */
+void __fcoe_get_lesb(struct fc_lport *lport,
+		     struct fc_els_lesb *fc_lesb,
+		     struct net_device *netdev)
+{
+	unsigned int cpu;
+	u32 lfc, vlfc, mdac;
+	struct fc_stats *stats;
+	struct fcoe_fc_els_lesb *lesb;
+	struct rtnl_link_stats64 temp;
+
+	lfc = 0;
+	vlfc = 0;
+	mdac = 0;
+	lesb = (struct fcoe_fc_els_lesb *)fc_lesb;
+	memset(lesb, 0, sizeof(*lesb));
+	for_each_possible_cpu(cpu) {
+		stats = per_cpu_ptr(lport->stats, cpu);
+		lfc += stats->LinkFailureCount;
+		vlfc += stats->VLinkFailureCount;
+		mdac += stats->MissDiscAdvCount;
+	}
+	lesb->lesb_link_fail = htonl(lfc);
+	lesb->lesb_vlink_fail = htonl(vlfc);
+	lesb->lesb_miss_fka = htonl(mdac);
+	lesb->lesb_fcs_error =
+			htonl(dev_get_stats(netdev, &temp)->rx_crc_errors);
+}
+EXPORT_SYMBOL_GPL(__fcoe_get_lesb);
+
+/**
+ * fcoe_get_lesb() - Fill the FCoE Link Error Status Block
+ * @lport: the local port
+ * @fc_lesb: the link error status block
+ */
+void fcoe_get_lesb(struct fc_lport *lport,
+			 struct fc_els_lesb *fc_lesb)
+{
+	struct net_device *netdev = fcoe_get_netdev(lport);
+
+	__fcoe_get_lesb(lport, fc_lesb, netdev);
+}
+EXPORT_SYMBOL_GPL(fcoe_get_lesb);
+
+/**
+ * fcoe_ctlr_get_lesb() - Get the Link Error Status Block (LESB) for a given
+ * fcoe controller device
+ * @ctlr_dev: The given fcoe controller device
+ *
+ */
+void fcoe_ctlr_get_lesb(struct fcoe_ctlr_device *ctlr_dev)
+{
+	struct fcoe_ctlr *fip = fcoe_ctlr_device_priv(ctlr_dev);
+	struct net_device *netdev = fcoe_get_netdev(fip->lp);
+	struct fc_els_lesb *fc_lesb;
+
+	fc_lesb = (struct fc_els_lesb *)(&ctlr_dev->lesb);
+	__fcoe_get_lesb(fip->lp, fc_lesb, netdev);
+}
+EXPORT_SYMBOL_GPL(fcoe_ctlr_get_lesb);
+
+void fcoe_wwn_to_str(u64 wwn, char *buf, int len)
+{
+	u8 wwpn[8];
+
+	u64_to_wwn(wwn, wwpn);
+	snprintf(buf, len, "%02x%02x%02x%02x%02x%02x%02x%02x",
+		 wwpn[0], wwpn[1], wwpn[2], wwpn[3],
+		 wwpn[4], wwpn[5], wwpn[6], wwpn[7]);
+}
+EXPORT_SYMBOL_GPL(fcoe_wwn_to_str);
+
+/**
+ * fcoe_validate_vport_create() - Validate a vport before creating it
+ * @vport: NPIV port to be created
+ *
+ * This routine is meant to add validation for a vport before creating it
+ * via fcoe_vport_create().
+ * Current validations are:
+ *      - WWPN supplied is unique for given lport
+ */
+int fcoe_validate_vport_create(struct fc_vport *vport)
+{
+	struct Scsi_Host *shost = vport_to_shost(vport);
+	struct fc_lport *n_port = shost_priv(shost);
+	struct fc_lport *vn_port;
+	int rc = 0;
+	char buf[32];
+
+	mutex_lock(&n_port->lp_mutex);
+
+	fcoe_wwn_to_str(vport->port_name, buf, sizeof(buf));
+	/* Check if the wwpn is not same as that of the lport */
+	if (!memcmp(&n_port->wwpn, &vport->port_name, sizeof(u64))) {
+		LIBFCOE_TRANSPORT_DBG("vport WWPN 0x%s is same as that of the "
+				      "base port WWPN\n", buf);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	/* Check if there is any existing vport with same wwpn */
+	list_for_each_entry(vn_port, &n_port->vports, list) {
+		if (!memcmp(&vn_port->wwpn, &vport->port_name, sizeof(u64))) {
+			LIBFCOE_TRANSPORT_DBG("vport with given WWPN 0x%s "
+					      "already exists\n", buf);
+			rc = -EINVAL;
+			break;
+		}
+	}
+out:
+	mutex_unlock(&n_port->lp_mutex);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(fcoe_validate_vport_create);
+
+/**
+ * fcoe_get_wwn() - Get the world wide name from LLD if it supports it
+ * @netdev: the associated net device
+ * @wwn: the output WWN
+ * @type: the type of WWN (WWPN or WWNN)
+ *
+ * Returns: 0 for success
+ */
+int fcoe_get_wwn(struct net_device *netdev, u64 *wwn, int type)
+{
+	const struct net_device_ops *ops = netdev->netdev_ops;
+
+	if (ops->ndo_fcoe_get_wwn)
+		return ops->ndo_fcoe_get_wwn(netdev, wwn, type);
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(fcoe_get_wwn);
+
+/**
+ * fcoe_fc_crc() - Calculates the CRC for a given frame
+ * @fp: The frame to be checksumed
+ *
+ * This uses crc32() routine to calculate the CRC for a frame
+ *
+ * Return: The 32 bit CRC value
+ */
+u32 fcoe_fc_crc(struct fc_frame *fp)
+{
+	struct sk_buff *skb = fp_skb(fp);
+	struct skb_frag_struct *frag;
+	unsigned char *data;
+	unsigned long off, len, clen;
+	u32 crc;
+	unsigned i;
+
+	crc = crc32(~0, skb->data, skb_headlen(skb));
+
+	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+		frag = &skb_shinfo(skb)->frags[i];
+		off = frag->page_offset;
+		len = skb_frag_size(frag);
+		while (len > 0) {
+			clen = min(len, PAGE_SIZE - (off & ~PAGE_MASK));
+			data = kmap_atomic(
+				skb_frag_page(frag) + (off >> PAGE_SHIFT));
+			crc = crc32(crc, data + (off & ~PAGE_MASK), clen);
+			kunmap_atomic(data);
+			off += clen;
+			len -= clen;
+		}
+	}
+	return crc;
+}
+EXPORT_SYMBOL_GPL(fcoe_fc_crc);
+
+/**
+ * fcoe_start_io() - Start FCoE I/O
+ * @skb: The packet to be transmitted
+ *
+ * This routine is called from the net device to start transmitting
+ * FCoE packets.
+ *
+ * Returns: 0 for success
+ */
+int fcoe_start_io(struct sk_buff *skb)
+{
+	struct sk_buff *nskb;
+	int rc;
+
+	nskb = skb_clone(skb, GFP_ATOMIC);
+	if (!nskb)
+		return -ENOMEM;
+	rc = dev_queue_xmit(nskb);
+	if (rc != 0)
+		return rc;
+	kfree_skb(skb);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(fcoe_start_io);
+
+
+/**
+ * fcoe_clean_pending_queue() - Dequeue a skb and free it
+ * @lport: The local port to dequeue a skb on
+ */
+void fcoe_clean_pending_queue(struct fc_lport *lport)
+{
+	struct fcoe_port  *port = lport_priv(lport);
+	struct sk_buff *skb;
+
+	spin_lock_bh(&port->fcoe_pending_queue.lock);
+	while ((skb = __skb_dequeue(&port->fcoe_pending_queue)) != NULL) {
+		spin_unlock_bh(&port->fcoe_pending_queue.lock);
+		kfree_skb(skb);
+		spin_lock_bh(&port->fcoe_pending_queue.lock);
+	}
+	spin_unlock_bh(&port->fcoe_pending_queue.lock);
+}
+EXPORT_SYMBOL_GPL(fcoe_clean_pending_queue);
+
+/**
+ * fcoe_check_wait_queue() - Attempt to clear the transmit backlog
+ * @lport: The local port whose backlog is to be cleared
+ *
+ * This empties the wait_queue, dequeues the head of the wait_queue queue
+ * and calls fcoe_start_io() for each packet. If all skb have been
+ * transmitted it returns the qlen. If an error occurs it restores
+ * wait_queue (to try again later) and returns -1.
+ *
+ * The wait_queue is used when the skb transmit fails. The failed skb
+ * will go in the wait_queue which will be emptied by the timer function or
+ * by the next skb transmit.
+ */
+void fcoe_check_wait_queue(struct fc_lport *lport, struct sk_buff *skb)
+{
+	struct fcoe_port *port = lport_priv(lport);
+	int rc;
+
+	spin_lock_bh(&port->fcoe_pending_queue.lock);
+
+	if (skb)
+		__skb_queue_tail(&port->fcoe_pending_queue, skb);
+
+	if (port->fcoe_pending_queue_active)
+		goto out;
+	port->fcoe_pending_queue_active = 1;
+
+	while (port->fcoe_pending_queue.qlen) {
+		/* keep qlen > 0 until fcoe_start_io succeeds */
+		port->fcoe_pending_queue.qlen++;
+		skb = __skb_dequeue(&port->fcoe_pending_queue);
+
+		spin_unlock_bh(&port->fcoe_pending_queue.lock);
+		rc = fcoe_start_io(skb);
+		spin_lock_bh(&port->fcoe_pending_queue.lock);
+
+		if (rc) {
+			__skb_queue_head(&port->fcoe_pending_queue, skb);
+			/* undo temporary increment above */
+			port->fcoe_pending_queue.qlen--;
+			break;
+		}
+		/* undo temporary increment above */
+		port->fcoe_pending_queue.qlen--;
+	}
+
+	if (port->fcoe_pending_queue.qlen < port->min_queue_depth)
+		lport->qfull = 0;
+	if (port->fcoe_pending_queue.qlen && !timer_pending(&port->timer))
+		mod_timer(&port->timer, jiffies + 2);
+	port->fcoe_pending_queue_active = 0;
+out:
+	if (port->fcoe_pending_queue.qlen > port->max_queue_depth)
+		lport->qfull = 1;
+	spin_unlock_bh(&port->fcoe_pending_queue.lock);
+}
+EXPORT_SYMBOL_GPL(fcoe_check_wait_queue);
+
+/**
+ * fcoe_queue_timer() - The fcoe queue timer
+ * @lport: The local port
+ *
+ * Calls fcoe_check_wait_queue on timeout
+ */
+void fcoe_queue_timer(struct timer_list *t)
+{
+	struct fcoe_port *port = from_timer(port, t, timer);
+
+	fcoe_check_wait_queue(port->lport, NULL);
+}
+EXPORT_SYMBOL_GPL(fcoe_queue_timer);
+
+/**
+ * fcoe_get_paged_crc_eof() - Allocate a page to be used for the trailer CRC
+ * @skb:  The packet to be transmitted
+ * @tlen: The total length of the trailer
+ * @fps:  The fcoe context
+ *
+ * This routine allocates a page for frame trailers. The page is re-used if
+ * there is enough room left on it for the current trailer. If there isn't
+ * enough buffer left a new page is allocated for the trailer. Reference to
+ * the page from this function as well as the skbs using the page fragments
+ * ensure that the page is freed at the appropriate time.
+ *
+ * Returns: 0 for success
+ */
+int fcoe_get_paged_crc_eof(struct sk_buff *skb, int tlen,
+			   struct fcoe_percpu_s *fps)
+{
+	struct page *page;
+
+	page = fps->crc_eof_page;
+	if (!page) {
+		page = alloc_page(GFP_ATOMIC);
+		if (!page)
+			return -ENOMEM;
+
+		fps->crc_eof_page = page;
+		fps->crc_eof_offset = 0;
+	}
+
+	get_page(page);
+	skb_fill_page_desc(skb, skb_shinfo(skb)->nr_frags, page,
+			   fps->crc_eof_offset, tlen);
+	skb->len += tlen;
+	skb->data_len += tlen;
+	skb->truesize += tlen;
+	fps->crc_eof_offset += sizeof(struct fcoe_crc_eof);
+
+	if (fps->crc_eof_offset >= PAGE_SIZE) {
+		fps->crc_eof_page = NULL;
+		fps->crc_eof_offset = 0;
+		put_page(page);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(fcoe_get_paged_crc_eof);
+
+/**
+ * fcoe_transport_lookup - find an fcoe transport that matches a netdev
+ * @netdev: The netdev to look for from all attached transports
+ *
+ * Returns : ptr to the fcoe transport that supports this netdev or NULL
+ * if not found.
+ *
+ * The ft_mutex should be held when this is called
+ */
+static struct fcoe_transport *fcoe_transport_lookup(struct net_device *netdev)
+{
+	struct fcoe_transport *ft = NULL;
+
+	list_for_each_entry(ft, &fcoe_transports, list)
+		if (ft->match && ft->match(netdev))
+			return ft;
+	return NULL;
+}
+
+/**
+ * fcoe_transport_attach - Attaches an FCoE transport
+ * @ft: The fcoe transport to be attached
+ *
+ * Returns : 0 for success
+ */
+int fcoe_transport_attach(struct fcoe_transport *ft)
+{
+	int rc = 0;
+
+	mutex_lock(&ft_mutex);
+	if (ft->attached) {
+		LIBFCOE_TRANSPORT_DBG("transport %s already attached\n",
+				       ft->name);
+		rc = -EEXIST;
+		goto out_attach;
+	}
+
+	/* Add default transport to the tail */
+	if (strcmp(ft->name, FCOE_TRANSPORT_DEFAULT))
+		list_add(&ft->list, &fcoe_transports);
+	else
+		list_add_tail(&ft->list, &fcoe_transports);
+
+	ft->attached = true;
+	LIBFCOE_TRANSPORT_DBG("attaching transport %s\n", ft->name);
+
+out_attach:
+	mutex_unlock(&ft_mutex);
+	return rc;
+}
+EXPORT_SYMBOL(fcoe_transport_attach);
+
+/**
+ * fcoe_transport_detach - Detaches an FCoE transport
+ * @ft: The fcoe transport to be attached
+ *
+ * Returns : 0 for success
+ */
+int fcoe_transport_detach(struct fcoe_transport *ft)
+{
+	int rc = 0;
+	struct fcoe_netdev_mapping *nm = NULL, *tmp;
+
+	mutex_lock(&ft_mutex);
+	if (!ft->attached) {
+		LIBFCOE_TRANSPORT_DBG("transport %s already detached\n",
+			ft->name);
+		rc = -ENODEV;
+		goto out_attach;
+	}
+
+	/* remove netdev mapping for this transport as it is going away */
+	mutex_lock(&fn_mutex);
+	list_for_each_entry_safe(nm, tmp, &fcoe_netdevs, list) {
+		if (nm->ft == ft) {
+			LIBFCOE_TRANSPORT_DBG("transport %s going away, "
+				"remove its netdev mapping for %s\n",
+				ft->name, nm->netdev->name);
+			list_del(&nm->list);
+			kfree(nm);
+		}
+	}
+	mutex_unlock(&fn_mutex);
+
+	list_del(&ft->list);
+	ft->attached = false;
+	LIBFCOE_TRANSPORT_DBG("detaching transport %s\n", ft->name);
+
+out_attach:
+	mutex_unlock(&ft_mutex);
+	return rc;
+
+}
+EXPORT_SYMBOL(fcoe_transport_detach);
+
+static int fcoe_transport_show(char *buffer, const struct kernel_param *kp)
+{
+	int i, j;
+	struct fcoe_transport *ft = NULL;
+
+	i = j = sprintf(buffer, "Attached FCoE transports:");
+	mutex_lock(&ft_mutex);
+	list_for_each_entry(ft, &fcoe_transports, list) {
+		if (i >= PAGE_SIZE - IFNAMSIZ)
+			break;
+		i += snprintf(&buffer[i], IFNAMSIZ, "%s ", ft->name);
+	}
+	mutex_unlock(&ft_mutex);
+	if (i == j)
+		i += snprintf(&buffer[i], IFNAMSIZ, "none");
+	return i;
+}
+
+static int __init fcoe_transport_init(void)
+{
+	register_netdevice_notifier(&libfcoe_notifier);
+	return 0;
+}
+
+static int fcoe_transport_exit(void)
+{
+	struct fcoe_transport *ft;
+
+	unregister_netdevice_notifier(&libfcoe_notifier);
+	mutex_lock(&ft_mutex);
+	list_for_each_entry(ft, &fcoe_transports, list)
+		printk(KERN_ERR "FCoE transport %s is still attached!\n",
+		      ft->name);
+	mutex_unlock(&ft_mutex);
+	return 0;
+}
+
+
+static int fcoe_add_netdev_mapping(struct net_device *netdev,
+					struct fcoe_transport *ft)
+{
+	struct fcoe_netdev_mapping *nm;
+
+	nm = kmalloc(sizeof(*nm), GFP_KERNEL);
+	if (!nm) {
+		printk(KERN_ERR "Unable to allocate netdev_mapping");
+		return -ENOMEM;
+	}
+
+	nm->netdev = netdev;
+	nm->ft = ft;
+
+	mutex_lock(&fn_mutex);
+	list_add(&nm->list, &fcoe_netdevs);
+	mutex_unlock(&fn_mutex);
+	return 0;
+}
+
+
+static void fcoe_del_netdev_mapping(struct net_device *netdev)
+{
+	struct fcoe_netdev_mapping *nm = NULL, *tmp;
+
+	mutex_lock(&fn_mutex);
+	list_for_each_entry_safe(nm, tmp, &fcoe_netdevs, list) {
+		if (nm->netdev == netdev) {
+			list_del(&nm->list);
+			kfree(nm);
+			mutex_unlock(&fn_mutex);
+			return;
+		}
+	}
+	mutex_unlock(&fn_mutex);
+}
+
+
+/**
+ * fcoe_netdev_map_lookup - find the fcoe transport that matches the netdev on which
+ * it was created
+ *
+ * Returns : ptr to the fcoe transport that supports this netdev or NULL
+ * if not found.
+ *
+ * The ft_mutex should be held when this is called
+ */
+static struct fcoe_transport *fcoe_netdev_map_lookup(struct net_device *netdev)
+{
+	struct fcoe_transport *ft = NULL;
+	struct fcoe_netdev_mapping *nm;
+
+	mutex_lock(&fn_mutex);
+	list_for_each_entry(nm, &fcoe_netdevs, list) {
+		if (netdev == nm->netdev) {
+			ft = nm->ft;
+			mutex_unlock(&fn_mutex);
+			return ft;
+		}
+	}
+
+	mutex_unlock(&fn_mutex);
+	return NULL;
+}
+
+/**
+ * fcoe_if_to_netdev() - Parse a name buffer to get a net device
+ * @buffer: The name of the net device
+ *
+ * Returns: NULL or a ptr to net_device
+ */
+static struct net_device *fcoe_if_to_netdev(const char *buffer)
+{
+	char *cp;
+	char ifname[IFNAMSIZ + 2];
+
+	if (buffer) {
+		strlcpy(ifname, buffer, IFNAMSIZ);
+		cp = ifname + strlen(ifname);
+		while (--cp >= ifname && *cp == '\n')
+			*cp = '\0';
+		return dev_get_by_name(&init_net, ifname);
+	}
+	return NULL;
+}
+
+/**
+ * libfcoe_device_notification() - Handler for net device events
+ * @notifier: The context of the notification
+ * @event:    The type of event
+ * @ptr:      The net device that the event was on
+ *
+ * This function is called by the Ethernet driver in case of link change event.
+ *
+ * Returns: 0 for success
+ */
+static int libfcoe_device_notification(struct notifier_block *notifier,
+				    ulong event, void *ptr)
+{
+	struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
+
+	switch (event) {
+	case NETDEV_UNREGISTER:
+		LIBFCOE_TRANSPORT_DBG("NETDEV_UNREGISTER %s\n",
+				      netdev->name);
+		fcoe_del_netdev_mapping(netdev);
+		break;
+	}
+	return NOTIFY_OK;
+}
+
+ssize_t fcoe_ctlr_create_store(struct bus_type *bus,
+			       const char *buf, size_t count)
+{
+	struct net_device *netdev = NULL;
+	struct fcoe_transport *ft = NULL;
+	int rc = 0;
+	int err;
+
+	mutex_lock(&ft_mutex);
+
+	netdev = fcoe_if_to_netdev(buf);
+	if (!netdev) {
+		LIBFCOE_TRANSPORT_DBG("Invalid device %s.\n", buf);
+		rc = -ENODEV;
+		goto out_nodev;
+	}
+
+	ft = fcoe_netdev_map_lookup(netdev);
+	if (ft) {
+		LIBFCOE_TRANSPORT_DBG("transport %s already has existing "
+				      "FCoE instance on %s.\n",
+				      ft->name, netdev->name);
+		rc = -EEXIST;
+		goto out_putdev;
+	}
+
+	ft = fcoe_transport_lookup(netdev);
+	if (!ft) {
+		LIBFCOE_TRANSPORT_DBG("no FCoE transport found for %s.\n",
+				      netdev->name);
+		rc = -ENODEV;
+		goto out_putdev;
+	}
+
+	/* pass to transport create */
+	err = ft->alloc ? ft->alloc(netdev) : -ENODEV;
+	if (err) {
+		fcoe_del_netdev_mapping(netdev);
+		rc = -ENOMEM;
+		goto out_putdev;
+	}
+
+	err = fcoe_add_netdev_mapping(netdev, ft);
+	if (err) {
+		LIBFCOE_TRANSPORT_DBG("failed to add new netdev mapping "
+				      "for FCoE transport %s for %s.\n",
+				      ft->name, netdev->name);
+		rc = -ENODEV;
+		goto out_putdev;
+	}
+
+	LIBFCOE_TRANSPORT_DBG("transport %s succeeded to create fcoe on %s.\n",
+			      ft->name, netdev->name);
+
+out_putdev:
+	dev_put(netdev);
+out_nodev:
+	mutex_unlock(&ft_mutex);
+	if (rc)
+		return rc;
+	return count;
+}
+
+ssize_t fcoe_ctlr_destroy_store(struct bus_type *bus,
+				const char *buf, size_t count)
+{
+	int rc = -ENODEV;
+	struct net_device *netdev = NULL;
+	struct fcoe_transport *ft = NULL;
+
+	mutex_lock(&ft_mutex);
+
+	netdev = fcoe_if_to_netdev(buf);
+	if (!netdev) {
+		LIBFCOE_TRANSPORT_DBG("invalid device %s.\n", buf);
+		goto out_nodev;
+	}
+
+	ft = fcoe_netdev_map_lookup(netdev);
+	if (!ft) {
+		LIBFCOE_TRANSPORT_DBG("no FCoE transport found for %s.\n",
+				      netdev->name);
+		goto out_putdev;
+	}
+
+	/* pass to transport destroy */
+	rc = ft->destroy(netdev);
+	if (rc)
+		goto out_putdev;
+
+	fcoe_del_netdev_mapping(netdev);
+	LIBFCOE_TRANSPORT_DBG("transport %s %s to destroy fcoe on %s.\n",
+			      ft->name, (rc) ? "failed" : "succeeded",
+			      netdev->name);
+	rc = count; /* required for successful return */
+out_putdev:
+	dev_put(netdev);
+out_nodev:
+	mutex_unlock(&ft_mutex);
+	return rc;
+}
+EXPORT_SYMBOL(fcoe_ctlr_destroy_store);
+
+/**
+ * fcoe_transport_create() - Create a fcoe interface
+ * @buffer: The name of the Ethernet interface to create on
+ * @kp:	    The associated kernel param
+ *
+ * Called from sysfs. This holds the ft_mutex while calling the
+ * registered fcoe transport's create function.
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_transport_create(const char *buffer,
+				 const struct kernel_param *kp)
+{
+	int rc = -ENODEV;
+	struct net_device *netdev = NULL;
+	struct fcoe_transport *ft = NULL;
+	enum fip_state fip_mode = (enum fip_state)(long)kp->arg;
+
+	mutex_lock(&ft_mutex);
+
+	netdev = fcoe_if_to_netdev(buffer);
+	if (!netdev) {
+		LIBFCOE_TRANSPORT_DBG("Invalid device %s.\n", buffer);
+		goto out_nodev;
+	}
+
+	ft = fcoe_netdev_map_lookup(netdev);
+	if (ft) {
+		LIBFCOE_TRANSPORT_DBG("transport %s already has existing "
+				      "FCoE instance on %s.\n",
+				      ft->name, netdev->name);
+		rc = -EEXIST;
+		goto out_putdev;
+	}
+
+	ft = fcoe_transport_lookup(netdev);
+	if (!ft) {
+		LIBFCOE_TRANSPORT_DBG("no FCoE transport found for %s.\n",
+				      netdev->name);
+		goto out_putdev;
+	}
+
+	rc = fcoe_add_netdev_mapping(netdev, ft);
+	if (rc) {
+		LIBFCOE_TRANSPORT_DBG("failed to add new netdev mapping "
+				      "for FCoE transport %s for %s.\n",
+				      ft->name, netdev->name);
+		goto out_putdev;
+	}
+
+	/* pass to transport create */
+	rc = ft->create ? ft->create(netdev, fip_mode) : -ENODEV;
+	if (rc)
+		fcoe_del_netdev_mapping(netdev);
+
+	LIBFCOE_TRANSPORT_DBG("transport %s %s to create fcoe on %s.\n",
+			      ft->name, (rc) ? "failed" : "succeeded",
+			      netdev->name);
+
+out_putdev:
+	dev_put(netdev);
+out_nodev:
+	mutex_unlock(&ft_mutex);
+	return rc;
+}
+
+/**
+ * fcoe_transport_destroy() - Destroy a FCoE interface
+ * @buffer: The name of the Ethernet interface to be destroyed
+ * @kp:	    The associated kernel parameter
+ *
+ * Called from sysfs. This holds the ft_mutex while calling the
+ * registered fcoe transport's destroy function.
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_transport_destroy(const char *buffer,
+				  const struct kernel_param *kp)
+{
+	int rc = -ENODEV;
+	struct net_device *netdev = NULL;
+	struct fcoe_transport *ft = NULL;
+
+	mutex_lock(&ft_mutex);
+
+	netdev = fcoe_if_to_netdev(buffer);
+	if (!netdev) {
+		LIBFCOE_TRANSPORT_DBG("invalid device %s.\n", buffer);
+		goto out_nodev;
+	}
+
+	ft = fcoe_netdev_map_lookup(netdev);
+	if (!ft) {
+		LIBFCOE_TRANSPORT_DBG("no FCoE transport found for %s.\n",
+				      netdev->name);
+		goto out_putdev;
+	}
+
+	/* pass to transport destroy */
+	rc = ft->destroy ? ft->destroy(netdev) : -ENODEV;
+	fcoe_del_netdev_mapping(netdev);
+	LIBFCOE_TRANSPORT_DBG("transport %s %s to destroy fcoe on %s.\n",
+			      ft->name, (rc) ? "failed" : "succeeded",
+			      netdev->name);
+
+out_putdev:
+	dev_put(netdev);
+out_nodev:
+	mutex_unlock(&ft_mutex);
+	return rc;
+}
+
+/**
+ * fcoe_transport_disable() - Disables a FCoE interface
+ * @buffer: The name of the Ethernet interface to be disabled
+ * @kp:	    The associated kernel parameter
+ *
+ * Called from sysfs.
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_transport_disable(const char *buffer,
+				  const struct kernel_param *kp)
+{
+	int rc = -ENODEV;
+	struct net_device *netdev = NULL;
+	struct fcoe_transport *ft = NULL;
+
+	mutex_lock(&ft_mutex);
+
+	netdev = fcoe_if_to_netdev(buffer);
+	if (!netdev)
+		goto out_nodev;
+
+	ft = fcoe_netdev_map_lookup(netdev);
+	if (!ft)
+		goto out_putdev;
+
+	rc = ft->disable ? ft->disable(netdev) : -ENODEV;
+
+out_putdev:
+	dev_put(netdev);
+out_nodev:
+	mutex_unlock(&ft_mutex);
+	return rc;
+}
+
+/**
+ * fcoe_transport_enable() - Enables a FCoE interface
+ * @buffer: The name of the Ethernet interface to be enabled
+ * @kp:     The associated kernel parameter
+ *
+ * Called from sysfs.
+ *
+ * Returns: 0 for success
+ */
+static int fcoe_transport_enable(const char *buffer,
+				 const struct kernel_param *kp)
+{
+	int rc = -ENODEV;
+	struct net_device *netdev = NULL;
+	struct fcoe_transport *ft = NULL;
+
+	mutex_lock(&ft_mutex);
+
+	netdev = fcoe_if_to_netdev(buffer);
+	if (!netdev)
+		goto out_nodev;
+
+	ft = fcoe_netdev_map_lookup(netdev);
+	if (!ft)
+		goto out_putdev;
+
+	rc = ft->enable ? ft->enable(netdev) : -ENODEV;
+
+out_putdev:
+	dev_put(netdev);
+out_nodev:
+	mutex_unlock(&ft_mutex);
+	return rc;
+}
+
+/**
+ * libfcoe_init() - Initialization routine for libfcoe.ko
+ */
+static int __init libfcoe_init(void)
+{
+	int rc = 0;
+
+	rc = fcoe_transport_init();
+	if (rc)
+		return rc;
+
+	rc = fcoe_sysfs_setup();
+	if (rc)
+		fcoe_transport_exit();
+
+	return rc;
+}
+module_init(libfcoe_init);
+
+/**
+ * libfcoe_exit() - Tear down libfcoe.ko
+ */
+static void __exit libfcoe_exit(void)
+{
+	fcoe_sysfs_teardown();
+	fcoe_transport_exit();
+}
+module_exit(libfcoe_exit);
diff --git a/drivers/scsi/fcoe/libfcoe.h b/drivers/scsi/fcoe/libfcoe.h
new file mode 100644
index 0000000..b8bdfab
--- /dev/null
+++ b/drivers/scsi/fcoe/libfcoe.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _FCOE_LIBFCOE_H_
+#define _FCOE_LIBFCOE_H_
+
+extern unsigned int libfcoe_debug_logging;
+#define LIBFCOE_LOGGING	          0x01 /* General logging, not categorized */
+#define LIBFCOE_FIP_LOGGING       0x02 /* FIP logging */
+#define LIBFCOE_TRANSPORT_LOGGING 0x04 /* FCoE transport logging */
+#define LIBFCOE_SYSFS_LOGGING     0x08 /* fcoe_sysfs logging */
+
+#define LIBFCOE_CHECK_LOGGING(LEVEL, CMD)		\
+do {							\
+	if (unlikely(libfcoe_debug_logging & LEVEL))	\
+		do {					\
+			CMD;				\
+		} while (0);				\
+} while (0)
+
+#define LIBFCOE_DBG(fmt, args...)					\
+	LIBFCOE_CHECK_LOGGING(LIBFCOE_LOGGING,				\
+			      pr_info("libfcoe: " fmt, ##args);)
+
+#define LIBFCOE_FIP_DBG(fip, fmt, args...)				\
+	LIBFCOE_CHECK_LOGGING(LIBFCOE_FIP_LOGGING,			\
+			      pr_info("host%d: fip: " fmt,		\
+				      (fip)->lp->host->host_no, ##args);)
+
+#define LIBFCOE_TRANSPORT_DBG(fmt, args...)				\
+	LIBFCOE_CHECK_LOGGING(LIBFCOE_TRANSPORT_LOGGING,		\
+			      pr_info("%s: " fmt, __func__, ##args);)
+
+#define LIBFCOE_SYSFS_DBG(cdev, fmt, args...)				\
+	LIBFCOE_CHECK_LOGGING(LIBFCOE_SYSFS_LOGGING,			\
+			      pr_info("ctlr_%d: " fmt, cdev->id, ##args);)
+
+#endif /* _FCOE_LIBFCOE_H_ */