]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/scsi/fcoe/fcoe.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/fs/xfs/xfs
[karo-tx-linux.git] / drivers / scsi / fcoe / fcoe.c
index 01519c722edf9994a7f469b0b2ae1c97169d5f76..704b8e034946f9522b1ccd13c665a560371c0d8b 100644 (file)
@@ -55,9 +55,14 @@ 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).");
 
+DEFINE_MUTEX(fcoe_config_mutex);
+
+/* fcoe_percpu_clean completion.  Waiter protected by fcoe_create_mutex */
+static DECLARE_COMPLETION(fcoe_flush_completion);
+
 /* fcoe host list */
+/* must only by accessed under the RTNL mutex */
 LIST_HEAD(fcoe_hostlist);
-DEFINE_RWLOCK(fcoe_hostlist_lock);
 DEFINE_PER_CPU(struct fcoe_percpu_s, fcoe_percpu);
 
 /* Function Prototypes */
@@ -72,12 +77,13 @@ static int fcoe_link_ok(struct fc_lport *lp);
 
 static struct fc_lport *fcoe_hostlist_lookup(const struct net_device *);
 static int fcoe_hostlist_add(const struct fc_lport *);
-static int fcoe_hostlist_remove(const struct fc_lport *);
 
 static void fcoe_check_wait_queue(struct fc_lport *, struct sk_buff *);
 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 *dev);
 
 /* notification function from net device */
 static struct notifier_block fcoe_notifier = {
@@ -138,6 +144,180 @@ static struct scsi_host_template fcoe_shost_template = {
        .max_sectors = 0xffff,
 };
 
+static int fcoe_fip_recv(struct sk_buff *skb, struct net_device *dev,
+                        struct packet_type *ptype,
+                        struct net_device *orig_dev);
+/**
+ * fcoe_interface_setup()
+ * @fcoe: new fcoe_interface
+ * @netdev : ptr to the associated netdevice struct
+ *
+ * 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->ctlr;
+       struct netdev_hw_addr *ha;
+       u8 flogi_maddr[ETH_ALEN];
+
+       fcoe->netdev = netdev;
+
+       /* Do not support for bonding device */
+       if ((netdev->priv_flags & IFF_MASTER_ALB) ||
+           (netdev->priv_flags & IFF_SLAVE_INACTIVE) ||
+           (netdev->priv_flags & IFF_MASTER_8023AD)) {
+               return -EOPNOTSUPP;
+       }
+
+       /* look for SAN MAC address, if multiple SAN MACs exist, only
+        * use the first one for SPMA */
+       rcu_read_lock();
+       for_each_dev_addr(netdev, ha) {
+               if ((ha->type == NETDEV_HW_ADDR_T_SAN) &&
+                   (is_valid_ether_addr(fip->ctl_src_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_unicast_add(netdev, flogi_maddr);
+       if (fip->spma)
+               dev_unicast_add(netdev, fip->ctl_src_addr);
+       dev_mc_add(netdev, FIP_ALL_ENODE_MACS, ETH_ALEN, 0);
+
+       /*
+        * 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 = __constant_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);
+
+       return 0;
+}
+
+static void fcoe_fip_send(struct fcoe_ctlr *fip, struct sk_buff *skb);
+static void fcoe_update_src_mac(struct fcoe_ctlr *fip, u8 *old, u8 *new);
+static void fcoe_destroy_work(struct work_struct *work);
+
+/**
+ * fcoe_interface_create()
+ * @netdev: network interface
+ *
+ * Returns: pointer to a struct fcoe_interface or NULL on error
+ */
+static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev)
+{
+       struct fcoe_interface *fcoe;
+
+       fcoe = kzalloc(sizeof(*fcoe), GFP_KERNEL);
+       if (!fcoe) {
+               FCOE_NETDEV_DBG(netdev, "Could not allocate fcoe structure\n");
+               return NULL;
+       }
+
+       dev_hold(netdev);
+       kref_init(&fcoe->kref);
+
+       /*
+        * Initialize FIP.
+        */
+       fcoe_ctlr_init(&fcoe->ctlr);
+       fcoe->ctlr.send = fcoe_fip_send;
+       fcoe->ctlr.update_mac = fcoe_update_src_mac;
+
+       fcoe_interface_setup(fcoe, netdev);
+
+       return fcoe;
+}
+
+/**
+ * fcoe_interface_cleanup() - clean up netdev configurations
+ * @fcoe:
+ *
+ * Caller must be holding the RTNL mutex
+ */
+void fcoe_interface_cleanup(struct fcoe_interface *fcoe)
+{
+       struct net_device *netdev = fcoe->netdev;
+       struct fcoe_ctlr *fip = &fcoe->ctlr;
+       u8 flogi_maddr[ETH_ALEN];
+
+       /*
+        * 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);
+       synchronize_net();
+
+       /* Delete secondary MAC addresses */
+       memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN);
+       dev_unicast_delete(netdev, flogi_maddr);
+       if (!is_zero_ether_addr(fip->data_src_addr))
+               dev_unicast_delete(netdev, fip->data_src_addr);
+       if (fip->spma)
+               dev_unicast_delete(netdev, fip->ctl_src_addr);
+       dev_mc_delete(netdev, FIP_ALL_ENODE_MACS, ETH_ALEN, 0);
+}
+
+/**
+ * fcoe_interface_release() - fcoe_port kref release function
+ * @kref: embedded reference count in an fcoe_interface struct
+ */
+static void fcoe_interface_release(struct kref *kref)
+{
+       struct fcoe_interface *fcoe;
+       struct net_device *netdev;
+
+       fcoe = container_of(kref, struct fcoe_interface, kref);
+       netdev = fcoe->netdev;
+       /* tear-down the FCoE controller */
+       fcoe_ctlr_destroy(&fcoe->ctlr);
+       kfree(fcoe);
+       dev_put(netdev);
+}
+
+/**
+ * fcoe_interface_get()
+ * @fcoe:
+ */
+static inline void fcoe_interface_get(struct fcoe_interface *fcoe)
+{
+       kref_get(&fcoe->kref);
+}
+
+/**
+ * fcoe_interface_put()
+ * @fcoe:
+ */
+static inline void fcoe_interface_put(struct fcoe_interface *fcoe)
+{
+       kref_put(&fcoe->kref, fcoe_interface_release);
+}
+
 /**
  * fcoe_fip_recv - handle a received FIP frame.
  * @skb: the receive skb
@@ -222,31 +402,6 @@ static int fcoe_lport_config(struct fc_lport *lp)
        return 0;
 }
 
-/**
- * fcoe_netdev_cleanup() - clean up netdev configurations
- * @port: ptr to the fcoe_port
- */
-void fcoe_netdev_cleanup(struct fcoe_port *port)
-{
-       u8 flogi_maddr[ETH_ALEN];
-       struct fcoe_interface *fcoe = port->fcoe;
-
-       /* Don't listen for Ethernet packets anymore */
-       dev_remove_pack(&fcoe->fcoe_packet_type);
-       dev_remove_pack(&fcoe->fip_packet_type);
-
-       /* Delete secondary MAC addresses */
-       rtnl_lock();
-       memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN);
-       dev_unicast_delete(fcoe->netdev, flogi_maddr);
-       if (!is_zero_ether_addr(fcoe->ctlr.data_src_addr))
-               dev_unicast_delete(fcoe->netdev, fcoe->ctlr.data_src_addr);
-       if (fcoe->ctlr.spma)
-               dev_unicast_delete(fcoe->netdev, fcoe->ctlr.ctl_src_addr);
-       dev_mc_delete(fcoe->netdev, FIP_ALL_ENODE_MACS, ETH_ALEN, 0);
-       rtnl_unlock();
-}
-
 /**
  * fcoe_queue_timer() - fcoe queue timer
  * @lp: the fc_lport pointer
@@ -274,21 +429,10 @@ static int fcoe_netdev_config(struct fc_lport *lp, struct net_device *netdev)
        u64 wwnn, wwpn;
        struct fcoe_interface *fcoe;
        struct fcoe_port *port;
-       u8 flogi_maddr[ETH_ALEN];
-       struct netdev_hw_addr *ha;
 
        /* Setup lport private data to point to fcoe softc */
        port = lport_priv(lp);
        fcoe = port->fcoe;
-       fcoe->ctlr.lp = lp;
-       fcoe->netdev = netdev;
-
-       /* Do not support for bonding device */
-       if ((netdev->priv_flags & IFF_MASTER_ALB) ||
-           (netdev->priv_flags & IFF_SLAVE_INACTIVE) ||
-           (netdev->priv_flags & IFF_MASTER_8023AD)) {
-               return -EOPNOTSUPP;
-       }
 
        /*
         * Determine max frame size based on underlying device and optional
@@ -324,57 +468,12 @@ static int fcoe_netdev_config(struct fc_lport *lp, struct net_device *netdev)
        port->fcoe_pending_queue_active = 0;
        setup_timer(&port->timer, fcoe_queue_timer, (unsigned long)lp);
 
-       /* look for SAN MAC address, if multiple SAN MACs exist, only
-        * use the first one for SPMA */
-       rcu_read_lock();
-       for_each_dev_addr(netdev, ha) {
-               if ((ha->type == NETDEV_HW_ADDR_T_SAN) &&
-                   (is_valid_ether_addr(fcoe->ctlr.ctl_src_addr))) {
-                       memcpy(fcoe->ctlr.ctl_src_addr, ha->addr, ETH_ALEN);
-                       fcoe->ctlr.spma = 1;
-                       break;
-               }
-       }
-       rcu_read_unlock();
-
-       /* setup Source Mac Address */
-       if (!fcoe->ctlr.spma)
-               memcpy(fcoe->ctlr.ctl_src_addr, netdev->dev_addr,
-                      netdev->addr_len);
-
        wwnn = fcoe_wwn_from_mac(netdev->dev_addr, 1, 0);
        fc_set_wwnn(lp, wwnn);
        /* XXX - 3rd arg needs to be vlan id */
        wwpn = fcoe_wwn_from_mac(netdev->dev_addr, 2, 0);
        fc_set_wwpn(lp, wwpn);
 
-       /*
-        * Add FCoE MAC address as second unicast MAC address
-        * or enter promiscuous mode if not capable of listening
-        * for multiple unicast MACs.
-        */
-       rtnl_lock();
-       memcpy(flogi_maddr, (u8[6]) FC_FCOE_FLOGI_MAC, ETH_ALEN);
-       dev_unicast_add(netdev, flogi_maddr);
-       if (fcoe->ctlr.spma)
-               dev_unicast_add(netdev, fcoe->ctlr.ctl_src_addr);
-       dev_mc_add(netdev, FIP_ALL_ENODE_MACS, ETH_ALEN, 0);
-       rtnl_unlock();
-
-       /*
-        * 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 = __constant_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);
-
        return 0;
 }
 
@@ -431,14 +530,11 @@ bool fcoe_oem_match(struct fc_frame *fp)
  * fcoe_em_config() - allocates em for this lport
  * @lp: the fcoe that em is to allocated for
  *
- * Called with write fcoe_hostlist_lock held.
- *
  * Returns : 0 on success
  */
 static inline int fcoe_em_config(struct fc_lport *lp)
 {
        struct fcoe_port *port = lport_priv(lp);
-       struct fcoe_port *oldport = NULL;
        struct fcoe_interface *fcoe = port->fcoe;
        struct fcoe_interface *oldfcoe = NULL;
        struct net_device *old_real_dev, *cur_real_dev;
@@ -464,30 +560,29 @@ static inline int fcoe_em_config(struct fc_lport *lp)
                cur_real_dev = fcoe->netdev;
 
        list_for_each_entry(oldfcoe, &fcoe_hostlist, list) {
-               oldport = oldfcoe->priv;
                if (oldfcoe->netdev->priv_flags & IFF_802_1Q_VLAN)
                        old_real_dev = vlan_dev_real_dev(oldfcoe->netdev);
                else
                        old_real_dev = oldfcoe->netdev;
 
                if (cur_real_dev == old_real_dev) {
-                       port->oem = oldport->oem;
+                       fcoe->oem = oldfcoe->oem;
                        break;
                }
        }
 
-       if (port->oem) {
-               if (!fc_exch_mgr_add(lp, port->oem, fcoe_oem_match)) {
+       if (fcoe->oem) {
+               if (!fc_exch_mgr_add(lp, fcoe->oem, fcoe_oem_match)) {
                        printk(KERN_ERR "fcoe_em_config: failed to add "
                               "offload em:%p on interface:%s\n",
-                              port->oem, fcoe->netdev->name);
+                              fcoe->oem, fcoe->netdev->name);
                        return -ENOMEM;
                }
        } else {
-               port->oem = fc_exch_mgr_alloc(lp, FC_CLASS_3,
+               fcoe->oem = fc_exch_mgr_alloc(lp, FC_CLASS_3,
                                            FCOE_MIN_XID, lp->lro_xid,
                                            fcoe_oem_match);
-               if (!port->oem) {
+               if (!fcoe->oem) {
                        printk(KERN_ERR "fcoe_em_config: failed to allocate "
                               "em for offload exches on interface:%s\n",
                               fcoe->netdev->name);
@@ -525,22 +620,22 @@ static void fcoe_if_destroy(struct fc_lport *lport)
        /* Logout of the fabric */
        fc_fabric_logoff(lport);
 
-       /* Remove the instance from fcoe's list */
-       fcoe_hostlist_remove(lport);
+       /* Cleanup the fc_lport */
+       fc_lport_destroy(lport);
+       fc_fcp_destroy(lport);
 
-       /* clean up netdev configurations */
-       fcoe_netdev_cleanup(port);
+       /* Stop the transmit retry timer */
+       del_timer_sync(&port->timer);
 
-       /* tear-down the FCoE controller */
-       fcoe_ctlr_destroy(&fcoe->ctlr);
+       /* Free existing transmit skbs */
+       fcoe_clean_pending_queue(lport);
+
+       /* receives may not be stopped until after this */
+       fcoe_interface_put(fcoe);
 
        /* Free queued packets for the per-CPU receive threads */
        fcoe_percpu_clean(lport);
 
-       /* Cleanup the fc_lport */
-       fc_lport_destroy(lport);
-       fc_fcp_destroy(lport);
-
        /* Detach from the scsi-ml */
        fc_remove_host(lport->host);
        scsi_remove_host(lport->host);
@@ -548,19 +643,11 @@ static void fcoe_if_destroy(struct fc_lport *lport)
        /* There are no more rports or I/O, free the EM */
        fc_exch_mgr_free(lport);
 
-       /* Free existing skbs */
-       fcoe_clean_pending_queue(lport);
-
-       /* Stop the timer */
-       del_timer_sync(&port->timer);
-
        /* Free memory used by statistical counters */
        fc_lport_free_stats(lport);
 
-       /* Release the net_device and Scsi_Host */
-       dev_put(netdev);
+       /* Release the Scsi_Host */
        scsi_host_put(lport->host);
-       kfree(fcoe);            /* TODO, should be refcounted */
 }
 
 /*
@@ -606,45 +693,37 @@ static struct libfc_function_template fcoe_libfc_fcn_templ = {
 };
 
 /**
- * fcoe_if_create() - this function creates the fcoe interface
- * @netdev: pointer the associated netdevice
+ * fcoe_if_create() - this function creates the fcoe port
+ * @fcoe: fcoe_interface structure to create an fc_lport instance on
  * @parent: device pointer to be the parent in sysfs for the SCSI host
  *
- * Creates fc_lport struct and scsi_host for lport, configures lport
- * and starts fabric login.
+ * Creates fc_lport struct and scsi_host for lport, configures lport.
  *
  * Returns : The allocated fc_lport or an error pointer
  */
-static struct fc_lport *fcoe_if_create(struct net_device *netdev,
+static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe,
                                       struct device *parent)
 {
        int rc;
        struct fc_lport *lport = NULL;
        struct fcoe_port *port;
-       struct fcoe_interface *fcoe;
        struct Scsi_Host *shost;
+       struct net_device *netdev = fcoe->netdev;
 
        FCOE_NETDEV_DBG(netdev, "Create Interface\n");
 
-       fcoe = kzalloc(sizeof(*fcoe), GFP_KERNEL);
-       if (!fcoe) {
-               FCOE_NETDEV_DBG(netdev, "Could not allocate fcoe structure\n");
-               rc = -ENOMEM;
-               goto out;
-       }
-
        shost = libfc_host_alloc(&fcoe_shost_template,
                                 sizeof(struct fcoe_port));
        if (!shost) {
                FCOE_NETDEV_DBG(netdev, "Could not allocate host structure\n");
                rc = -ENOMEM;
-               goto out_kfree_port;
+               goto out;
        }
        lport = shost_priv(shost);
        port = lport_priv(lport);
-
+       port->lport = lport;
        port->fcoe = fcoe;
-       fcoe->priv = port;
+       INIT_WORK(&port->destroy_work, fcoe_destroy_work);
 
        /* configure fc_lport, e.g., em */
        rc = fcoe_lport_config(lport);
@@ -654,19 +733,12 @@ static struct fc_lport *fcoe_if_create(struct net_device *netdev,
                goto out_host_put;
        }
 
-       /*
-        * Initialize FIP.
-        */
-       fcoe_ctlr_init(&fcoe->ctlr);
-       fcoe->ctlr.send = fcoe_fip_send;
-       fcoe->ctlr.update_mac = fcoe_update_src_mac;
-
        /* 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_netdev_cleanup;
+               goto out_lp_destroy;
        }
 
        /* configure lport scsi host properties */
@@ -674,7 +746,7 @@ static struct fc_lport *fcoe_if_create(struct net_device *netdev,
        if (rc) {
                FCOE_NETDEV_DBG(netdev, "Could not configure shost for the "
                                "interface\n");
-               goto out_netdev_cleanup;
+               goto out_lp_destroy;
        }
 
        /* Initialize the library */
@@ -687,11 +759,13 @@ static struct fc_lport *fcoe_if_create(struct net_device *netdev,
 
        /*
         * fcoe_em_alloc() and fcoe_hostlist_add() both
-        * need to be atomic under fcoe_hostlist_lock
+        * 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.
         */
-       write_lock(&fcoe_hostlist_lock);
+
        /* lport exch manager allocation */
        rc = fcoe_em_config(lport);
        if (rc) {
@@ -700,29 +774,13 @@ static struct fc_lport *fcoe_if_create(struct net_device *netdev,
                goto out_lp_destroy;
        }
 
-       /* add to lports list */
-       fcoe_hostlist_add(lport);
-       write_unlock(&fcoe_hostlist_lock);
-
-       lport->boot_time = jiffies;
-
-       fc_fabric_login(lport);
-
-       if (!fcoe_link_ok(lport))
-               fcoe_ctlr_link_up(&fcoe->ctlr);
-
-       dev_hold(netdev);
-
+       fcoe_interface_get(fcoe);
        return lport;
 
 out_lp_destroy:
        fc_exch_mgr_free(lport);
-out_netdev_cleanup:
-       fcoe_netdev_cleanup(port);
 out_host_put:
        scsi_host_put(lport->host);
-out_kfree_port:
-       kfree(fcoe);
 out:
        return ERR_PTR(rc);
 }
@@ -754,6 +812,7 @@ static int __init fcoe_if_init(void)
 int __exit fcoe_if_exit(void)
 {
        fc_release_transport(scsi_transport_fcoe_sw);
+       scsi_transport_fcoe_sw = NULL;
        return 0;
 }
 
@@ -771,7 +830,7 @@ static void fcoe_percpu_thread_create(unsigned int cpu)
        thread = kthread_create(fcoe_percpu_receive_thread,
                                (void *)p, "fcoethread/%d", cpu);
 
-       if (likely(!IS_ERR(p->thread))) {
+       if (likely(!IS_ERR(thread))) {
                kthread_bind(thread, cpu);
                wake_up_process(thread);
 
@@ -1243,6 +1302,15 @@ int fcoe_xmit(struct fc_lport *lp, struct fc_frame *fp)
        return 0;
 }
 
+/**
+ * fcoe_percpu_flush_done() - Indicate percpu queue flush completion.
+ * @skb: the skb being completed.
+ */
+static void fcoe_percpu_flush_done(struct sk_buff *skb)
+{
+       complete(&fcoe_flush_completion);
+}
+
 /**
  * fcoe_percpu_receive_thread() - recv thread per cpu
  * @arg: ptr to the fcoe per cpu struct
@@ -1282,7 +1350,8 @@ int fcoe_percpu_receive_thread(void *arg)
                fr = fcoe_dev_from_skb(skb);
                lp = fr->fr_dev;
                if (unlikely(lp == NULL)) {
-                       FCOE_NETDEV_DBG(skb->dev, "Invalid HBA Structure");
+                       if (skb->destructor != fcoe_percpu_flush_done)
+                               FCOE_NETDEV_DBG(skb->dev, "NULL lport in skb");
                        kfree_skb(skb);
                        continue;
                }
@@ -1475,19 +1544,18 @@ static int fcoe_device_notification(struct notifier_block *notifier,
        struct fc_lport *lp = NULL;
        struct net_device *netdev = ptr;
        struct fcoe_interface *fcoe;
+       struct fcoe_port *port;
        struct fcoe_dev_stats *stats;
        u32 link_possible = 1;
        u32 mfs;
        int rc = NOTIFY_OK;
 
-       read_lock(&fcoe_hostlist_lock);
        list_for_each_entry(fcoe, &fcoe_hostlist, list) {
                if (fcoe->netdev == netdev) {
                        lp = fcoe->ctlr.lp;
                        break;
                }
        }
-       read_unlock(&fcoe_hostlist_lock);
        if (lp == NULL) {
                rc = NOTIFY_DONE;
                goto out;
@@ -1509,6 +1577,13 @@ static int fcoe_device_notification(struct notifier_block *notifier,
                break;
        case NETDEV_REGISTER:
                break;
+       case NETDEV_UNREGISTER:
+               list_del(&fcoe->list);
+               port = lport_priv(fcoe->ctlr.lp);
+               fcoe_interface_cleanup(fcoe);
+               schedule_work(&port->destroy_work);
+               goto out;
+               break;
        default:
                FCOE_NETDEV_DBG(netdev, "Unknown event %ld "
                                "from netdev netlink\n", event);
@@ -1545,75 +1620,6 @@ static struct net_device *fcoe_if_to_netdev(const char *buffer)
        return NULL;
 }
 
-/**
- * fcoe_netdev_to_module_owner() - finds out the driver module of the netdev
- * @netdev: the target netdev
- *
- * Returns: ptr to the struct module, NULL for failure
- */
-static struct module *
-fcoe_netdev_to_module_owner(const struct net_device *netdev)
-{
-       struct device *dev;
-
-       if (!netdev)
-               return NULL;
-
-       dev = netdev->dev.parent;
-       if (!dev)
-               return NULL;
-
-       if (!dev->driver)
-               return NULL;
-
-       return dev->driver->owner;
-}
-
-/**
- * fcoe_ethdrv_get() - Hold the Ethernet driver
- * @netdev: the target netdev
- *
- * Holds the Ethernet driver module by try_module_get() for
- * the corresponding netdev.
- *
- * Returns: 0 for success
- */
-static int fcoe_ethdrv_get(const struct net_device *netdev)
-{
-       struct module *owner;
-
-       owner = fcoe_netdev_to_module_owner(netdev);
-       if (owner) {
-               FCOE_NETDEV_DBG(netdev, "Hold driver module %s\n",
-                               module_name(owner));
-               return  try_module_get(owner);
-       }
-       return -ENODEV;
-}
-
-/**
- * fcoe_ethdrv_put() - Release the Ethernet driver
- * @netdev: the target netdev
- *
- * Releases the Ethernet driver module by module_put for
- * the corresponding netdev.
- *
- * Returns: 0 for success
- */
-static int fcoe_ethdrv_put(const struct net_device *netdev)
-{
-       struct module *owner;
-
-       owner = fcoe_netdev_to_module_owner(netdev);
-       if (owner) {
-               FCOE_NETDEV_DBG(netdev, "Release driver module %s\n",
-                               module_name(owner));
-               module_put(owner);
-               return 0;
-       }
-       return -ENODEV;
-}
-
 /**
  * fcoe_destroy() - handles the destroy from sysfs
  * @buffer: expected to be an eth if name
@@ -1623,30 +1629,57 @@ static int fcoe_ethdrv_put(const struct net_device *netdev)
  */
 static int fcoe_destroy(const char *buffer, struct kernel_param *kp)
 {
+       struct fcoe_interface *fcoe;
        struct net_device *netdev;
-       struct fc_lport *lport;
        int rc;
 
+       mutex_lock(&fcoe_config_mutex);
+#ifdef CONFIG_FCOE_MODULE
+       /*
+        * Make sure the module has been initialized, and is not about to be
+        * removed.  Module paramter sysfs files are writable before the
+        * module_init function is called and after module_exit.
+        */
+       if (THIS_MODULE->state != MODULE_STATE_LIVE) {
+               rc = -ENODEV;
+               goto out_nodev;
+       }
+#endif
+
        netdev = fcoe_if_to_netdev(buffer);
        if (!netdev) {
                rc = -ENODEV;
                goto out_nodev;
        }
-       /* look for existing lport */
-       lport = fcoe_hostlist_lookup(netdev);
-       if (!lport) {
+
+       rtnl_lock();
+       fcoe = fcoe_hostlist_lookup_port(netdev);
+       if (!fcoe) {
+               rtnl_unlock();
                rc = -ENODEV;
                goto out_putdev;
        }
-       fcoe_if_destroy(lport);
-       fcoe_ethdrv_put(netdev);
-       rc = 0;
+       list_del(&fcoe->list);
+       fcoe_interface_cleanup(fcoe);
+       rtnl_unlock();
+       fcoe_if_destroy(fcoe->ctlr.lp);
 out_putdev:
        dev_put(netdev);
 out_nodev:
+       mutex_unlock(&fcoe_config_mutex);
        return rc;
 }
 
+static void fcoe_destroy_work(struct work_struct *work)
+{
+       struct fcoe_port *port;
+
+       port = container_of(work, struct fcoe_port, destroy_work);
+       mutex_lock(&fcoe_config_mutex);
+       fcoe_if_destroy(port->lport);
+       mutex_unlock(&fcoe_config_mutex);
+}
+
 /**
  * fcoe_create() - Handles the create call from sysfs
  * @buffer: expected to be an eth if name
@@ -1657,33 +1690,75 @@ out_nodev:
 static int fcoe_create(const char *buffer, struct kernel_param *kp)
 {
        int rc;
+       struct fcoe_interface *fcoe;
        struct fc_lport *lport;
        struct net_device *netdev;
 
+       mutex_lock(&fcoe_config_mutex);
+#ifdef CONFIG_FCOE_MODULE
+       /*
+        * Make sure the module has been initialized, and is not about to be
+        * removed.  Module paramter sysfs files are writable before the
+        * module_init function is called and after module_exit.
+        */
+       if (THIS_MODULE->state != MODULE_STATE_LIVE) {
+               rc = -ENODEV;
+               goto out_nodev;
+       }
+#endif
+
+       rtnl_lock();
        netdev = fcoe_if_to_netdev(buffer);
        if (!netdev) {
                rc = -ENODEV;
                goto out_nodev;
        }
+
        /* look for existing lport */
        if (fcoe_hostlist_lookup(netdev)) {
                rc = -EEXIST;
                goto out_putdev;
        }
-       fcoe_ethdrv_get(netdev);
 
-       lport = fcoe_if_create(netdev, &netdev->dev);
+       fcoe = fcoe_interface_create(netdev);
+       if (!fcoe) {
+               rc = -ENOMEM;
+               goto out_putdev;
+       }
+
+       lport = fcoe_if_create(fcoe, &netdev->dev);
        if (IS_ERR(lport)) {
                printk(KERN_ERR "fcoe: Failed to create interface (%s)\n",
                       netdev->name);
-               fcoe_ethdrv_put(netdev);
                rc = -EIO;
-               goto out_putdev;
+               fcoe_interface_cleanup(fcoe);
+               goto out_free;
        }
+
+       /* Make this the "master" N_Port */
+       fcoe->ctlr.lp = lport;
+
+       /* add to lports list */
+       fcoe_hostlist_add(lport);
+
+       /* start FIP Discovery and FLOGI */
+       lport->boot_time = jiffies;
+       fc_fabric_login(lport);
+       if (!fcoe_link_ok(lport))
+               fcoe_ctlr_link_up(&fcoe->ctlr);
+
        rc = 0;
+out_free:
+       /*
+        * Release from init in fcoe_interface_create(), on success lport
+        * should be holding a reference taken in fcoe_if_create().
+        */
+       fcoe_interface_put(fcoe);
 out_putdev:
        dev_put(netdev);
 out_nodev:
+       rtnl_unlock();
+       mutex_unlock(&fcoe_config_mutex);
        return rc;
 }
 
@@ -1737,6 +1812,13 @@ int fcoe_link_ok(struct fc_lport *lp)
 /**
  * fcoe_percpu_clean() - Clear the pending skbs for an lport
  * @lp: the fc_lport
+ *
+ * Must be called with fcoe_create_mutex held to single-thread completion.
+ *
+ * This flushes the pending skbs by adding a new skb to each queue and
+ * waiting until they are all freed.  This assures us that not only are
+ * there no packets that will be handled by the lport, but also that any
+ * threads already handling packet have returned.
  */
 void fcoe_percpu_clean(struct fc_lport *lp)
 {
@@ -1761,7 +1843,25 @@ void fcoe_percpu_clean(struct fc_lport *lp)
                                kfree_skb(skb);
                        }
                }
+
+               if (!pp->thread || !cpu_online(cpu)) {
+                       spin_unlock_bh(&pp->fcoe_rx_list.lock);
+                       continue;
+               }
+
+               skb = dev_alloc_skb(0);
+               if (!skb) {
+                       spin_unlock_bh(&pp->fcoe_rx_list.lock);
+                       continue;
+               }
+               skb->destructor = fcoe_percpu_flush_done;
+
+               __skb_queue_tail(&pp->fcoe_rx_list, skb);
+               if (pp->fcoe_rx_list.qlen == 1)
+                       wake_up_process(pp->thread);
                spin_unlock_bh(&pp->fcoe_rx_list.lock);
+
+               wait_for_completion(&fcoe_flush_completion);
        }
 }
 
@@ -1802,9 +1902,8 @@ int fcoe_reset(struct Scsi_Host *shost)
  * fcoe_hostlist_lookup_port() - find the corresponding lport by a given device
  * @dev: this is currently ptr to net_device
  *
- * Called with fcoe_hostlist_lock held.
- *
  * Returns: NULL or the located fcoe_port
+ * Locking: must be called with the RNL mutex held
  */
 static struct fcoe_interface *
 fcoe_hostlist_lookup_port(const struct net_device *dev)
@@ -1823,15 +1922,13 @@ fcoe_hostlist_lookup_port(const struct net_device *dev)
  * @netdev: ptr to net_device
  *
  * Returns: 0 for success
+ * Locking: must be called with the RTNL mutex held
  */
-struct fc_lport *fcoe_hostlist_lookup(const struct net_device *netdev)
+static struct fc_lport *fcoe_hostlist_lookup(const struct net_device *netdev)
 {
        struct fcoe_interface *fcoe;
 
-       read_lock(&fcoe_hostlist_lock);
        fcoe = fcoe_hostlist_lookup_port(netdev);
-       read_unlock(&fcoe_hostlist_lock);
-
        return (fcoe) ? fcoe->ctlr.lp : NULL;
 }
 
@@ -1839,11 +1936,10 @@ struct fc_lport *fcoe_hostlist_lookup(const struct net_device *netdev)
  * fcoe_hostlist_add() - Add a lport to lports list
  * @lp: ptr to the fc_lport to be added
  *
- * Called with write fcoe_hostlist_lock held.
- *
  * Returns: 0 for success
+ * Locking: must be called with the RTNL mutex held
  */
-int fcoe_hostlist_add(const struct fc_lport *lport)
+static int fcoe_hostlist_add(const struct fc_lport *lport)
 {
        struct fcoe_interface *fcoe;
        struct fcoe_port *port;
@@ -1857,25 +1953,6 @@ int fcoe_hostlist_add(const struct fc_lport *lport)
        return 0;
 }
 
-/**
- * fcoe_hostlist_remove() - remove a lport from lports list
- * @lp: ptr to the fc_lport to be removed
- *
- * Returns: 0 for success
- */
-int fcoe_hostlist_remove(const struct fc_lport *lport)
-{
-       struct fcoe_interface *fcoe;
-
-       write_lock_bh(&fcoe_hostlist_lock);
-       fcoe = fcoe_hostlist_lookup_port(fcoe_netdev(lport));
-       BUG_ON(!fcoe);
-       list_del(&fcoe->list);
-       write_unlock_bh(&fcoe_hostlist_lock);
-
-       return 0;
-}
-
 /**
  * fcoe_init() - fcoe module loading initialization
  *
@@ -1887,6 +1964,8 @@ static int __init fcoe_init(void)
        int rc = 0;
        struct fcoe_percpu_s *p;
 
+       mutex_lock(&fcoe_config_mutex);
+
        for_each_possible_cpu(cpu) {
                p = &per_cpu(fcoe_percpu, cpu);
                skb_queue_head_init(&p->fcoe_rx_list);
@@ -1907,13 +1986,14 @@ static int __init fcoe_init(void)
        if (rc)
                goto out_free;
 
+       mutex_unlock(&fcoe_config_mutex);
        return 0;
 
 out_free:
        for_each_online_cpu(cpu) {
                fcoe_percpu_thread_destroy(cpu);
        }
-
+       mutex_unlock(&fcoe_config_mutex);
        return rc;
 }
 module_init(fcoe_init);
@@ -1927,19 +2007,35 @@ static void __exit fcoe_exit(void)
 {
        unsigned int cpu;
        struct fcoe_interface *fcoe, *tmp;
+       struct fcoe_port *port;
+
+       mutex_lock(&fcoe_config_mutex);
 
        fcoe_dev_cleanup();
 
        /* releases the associated fcoe hosts */
-       list_for_each_entry_safe(fcoe, tmp, &fcoe_hostlist, list)
-               fcoe_if_destroy(fcoe->ctlr.lp);
+       rtnl_lock();
+       list_for_each_entry_safe(fcoe, tmp, &fcoe_hostlist, list) {
+               list_del(&fcoe->list);
+               port = lport_priv(fcoe->ctlr.lp);
+               fcoe_interface_cleanup(fcoe);
+               schedule_work(&port->destroy_work);
+       }
+       rtnl_unlock();
 
        unregister_hotcpu_notifier(&fcoe_cpu_notifier);
 
        for_each_online_cpu(cpu)
                fcoe_percpu_thread_destroy(cpu);
 
-       /* detach from scsi transport */
+       mutex_unlock(&fcoe_config_mutex);
+
+       /* flush any asyncronous interface destroys,
+        * this should happen after the netdev notifier is unregistered */
+       flush_scheduled_work();
+
+       /* detach from scsi transport
+        * must happen after all destroys are done, therefor after the flush */
        fcoe_if_exit();
 }
 module_exit(fcoe_exit);