[SCSI] Merge scsi-misc-2.6 into scsi-rc-fixes-2.6
[safe/jmp/linux-2.6] / drivers / scsi / fcoe / fcoe.c
index 5615dfe..ba75a98 100644 (file)
@@ -26,6 +26,7 @@
 #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>
@@ -73,6 +74,7 @@ static int fcoe_rcv(struct sk_buff *, struct net_device *,
 static int fcoe_percpu_receive_thread(void *);
 static void fcoe_clean_pending_queue(struct fc_lport *);
 static void fcoe_percpu_clean(struct fc_lport *);
+static int fcoe_link_speed_update(struct fc_lport *);
 static int fcoe_link_ok(struct fc_lport *);
 
 static struct fc_lport *fcoe_hostlist_lookup(const struct net_device *);
@@ -101,9 +103,8 @@ static int fcoe_cpu_callback(struct notifier_block *, unsigned long, void *);
 
 static int fcoe_create(const char *, struct kernel_param *);
 static int fcoe_destroy(const char *, struct kernel_param *);
-
-static u8 *fcoe_get_src_mac(struct fc_lport *);
-static void fcoe_destroy_work(struct work_struct *);
+static int fcoe_enable(const char *, struct kernel_param *);
+static int fcoe_disable(const char *, struct kernel_param *);
 
 static struct fc_seq *fcoe_elsct_send(struct fc_lport *,
                                      u32 did, struct fc_frame *,
@@ -112,13 +113,22 @@ static struct fc_seq *fcoe_elsct_send(struct fc_lport *,
                                                   struct fc_frame *,
                                                   void *),
                                      void *, u32 timeout);
+static void fcoe_recv_frame(struct sk_buff *skb);
+
+static void fcoe_get_lesb(struct fc_lport *, struct fc_els_lesb *);
 
 module_param_call(create, fcoe_create, NULL, NULL, S_IWUSR);
 __MODULE_PARM_TYPE(create, "string");
-MODULE_PARM_DESC(create, "Create fcoe fcoe using net device passed in.");
+MODULE_PARM_DESC(create, " Creates fcoe instance on a ethernet interface");
 module_param_call(destroy, fcoe_destroy, NULL, NULL, S_IWUSR);
 __MODULE_PARM_TYPE(destroy, "string");
-MODULE_PARM_DESC(destroy, "Destroy fcoe fcoe");
+MODULE_PARM_DESC(destroy, " Destroys fcoe instance on a ethernet interface");
+module_param_call(enable, fcoe_enable, NULL, NULL, S_IWUSR);
+__MODULE_PARM_TYPE(enable, "string");
+MODULE_PARM_DESC(enable, " Enables fcoe on a ethernet interface.");
+module_param_call(disable, fcoe_disable, NULL, NULL, S_IWUSR);
+__MODULE_PARM_TYPE(disable, "string");
+MODULE_PARM_DESC(disable, " Disables fcoe on a ethernet interface.");
 
 /* notification function for packets from net device */
 static struct notifier_block fcoe_notifier = {
@@ -137,12 +147,15 @@ 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 struct libfc_function_template fcoe_libfc_fcn_templ = {
        .frame_send = fcoe_xmit,
        .ddp_setup = fcoe_ddp_setup,
        .ddp_done = fcoe_ddp_done,
        .elsct_send = fcoe_elsct_send,
+       .get_lesb = fcoe_get_lesb,
+       .lport_set_port_id = fcoe_set_port_id,
 };
 
 struct fc_function_template fcoe_transport_function = {
@@ -252,6 +265,7 @@ static int fcoe_interface_setup(struct fcoe_interface *fcoe,
 {
        struct fcoe_ctlr *fip = &fcoe->ctlr;
        struct netdev_hw_addr *ha;
+       struct net_device *real_dev;
        u8 flogi_maddr[ETH_ALEN];
        const struct net_device_ops *ops;
 
@@ -269,15 +283,18 @@ static int fcoe_interface_setup(struct fcoe_interface *fcoe,
        if ((netdev->priv_flags & IFF_MASTER_ALB) ||
            (netdev->priv_flags & IFF_SLAVE_INACTIVE) ||
            (netdev->priv_flags & IFF_MASTER_8023AD)) {
+               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 = (netdev->priv_flags & IFF_802_1Q_VLAN) ?
+               vlan_dev_real_dev(netdev) : netdev;
        rcu_read_lock();
-       for_each_dev_addr(netdev, ha) {
+       for_each_dev_addr(real_dev, ha) {
                if ((ha->type == NETDEV_HW_ADDR_T_SAN) &&
-                   (is_valid_ether_addr(fip->ctl_src_addr))) {
+                   (is_valid_ether_addr(ha->addr))) {
                        memcpy(fip->ctl_src_addr, ha->addr, ETH_ALEN);
                        fip->spma = 1;
                        break;
@@ -326,6 +343,7 @@ static int fcoe_interface_setup(struct fcoe_interface *fcoe,
 static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev)
 {
        struct fcoe_interface *fcoe;
+       int err;
 
        fcoe = kzalloc(sizeof(*fcoe), GFP_KERNEL);
        if (!fcoe) {
@@ -344,7 +362,13 @@ static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev)
        fcoe->ctlr.update_mac = fcoe_update_src_mac;
        fcoe->ctlr.get_src_addr = fcoe_get_src_mac;
 
-       fcoe_interface_setup(fcoe, netdev);
+       err = fcoe_interface_setup(fcoe, netdev);
+       if (err) {
+               fcoe_ctlr_destroy(&fcoe->ctlr);
+               kfree(fcoe);
+               dev_put(netdev);
+               return NULL;
+       }
 
        return fcoe;
 }
@@ -533,6 +557,23 @@ static void fcoe_queue_timer(ulong lport)
 }
 
 /**
+ * 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
+ */
+static 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;
+}
+
+/**
  * 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
@@ -547,6 +588,7 @@ static int fcoe_netdev_config(struct fc_lport *lport, struct net_device *netdev)
        u64 wwnn, wwpn;
        struct fcoe_interface *fcoe;
        struct fcoe_port *port;
+       int vid = 0;
 
        /* Setup lport private data to point to fcoe softc */
        port = lport_priv(lport);
@@ -590,11 +632,23 @@ static int fcoe_netdev_config(struct fc_lport *lport, struct net_device *netdev)
        port->fcoe_pending_queue_active = 0;
        setup_timer(&port->timer, fcoe_queue_timer, (unsigned long)lport);
 
+       fcoe_link_speed_update(lport);
+
        if (!lport->vport) {
-               wwnn = fcoe_wwn_from_mac(netdev->dev_addr, 1, 0);
+               /*
+                * Use NAA 1&2 (FC-FS Rev. 2.0, Sec. 15) to generate WWNN/WWPN:
+                * For WWNN, we use NAA 1 w/ bit 27-16 of word 0 as 0.
+                * For WWPN, we use NAA 2 w/ bit 27-16 of word 0 from VLAN ID
+                */
+               if (netdev->priv_flags & IFF_802_1Q_VLAN)
+                       vid = vlan_dev_vlan_id(netdev);
+
+               if (fcoe_get_wwn(netdev, &wwnn, NETDEV_FCOE_WWNN))
+                       wwnn = fcoe_wwn_from_mac(fcoe->ctlr.ctl_src_addr, 1, 0);
                fc_set_wwnn(lport, wwnn);
-               /* XXX - 3rd arg needs to be vlan id */
-               wwpn = fcoe_wwn_from_mac(netdev->dev_addr, 2, 0);
+               if (fcoe_get_wwn(netdev, &wwpn, NETDEV_FCOE_WWPN))
+                       wwpn = fcoe_wwn_from_mac(fcoe->ctlr.ctl_src_addr,
+                                                2, vid);
                fc_set_wwpn(lport, wwpn);
        }
 
@@ -604,15 +658,13 @@ static int fcoe_netdev_config(struct fc_lport *lport, struct net_device *netdev)
 /**
  * fcoe_shost_config() - Set up the SCSI host associated with a local port
  * @lport: The local port
- * @shost: The SCSI host to associate with 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 Scsi_Host *shost,
-                            struct device *dev)
+static int fcoe_shost_config(struct fc_lport *lport, struct device *dev)
 {
        int rc = 0;
 
@@ -620,6 +672,8 @@ static int fcoe_shost_config(struct fc_lport *lport, struct Scsi_Host *shost,
        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_transport_template;
        else
@@ -747,6 +801,12 @@ skip_oem:
 /**
  * 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 and RTNL mutex
+ * needed to be dropped by this function since not dropping RTNL
+ * would cause circular locking warning on synchronous fip worker
+ * cancelling thru fcoe_interface_put invoked by this function.
+ *
  */
 static void fcoe_if_destroy(struct fc_lport *lport)
 {
@@ -769,7 +829,6 @@ static void fcoe_if_destroy(struct fc_lport *lport)
        /* Free existing transmit skbs */
        fcoe_clean_pending_queue(lport);
 
-       rtnl_lock();
        if (!is_zero_ether_addr(port->data_src_addr))
                dev_unicast_delete(netdev, port->data_src_addr);
        rtnl_unlock();
@@ -792,6 +851,7 @@ static void fcoe_if_destroy(struct fc_lport *lport)
 
        /* Release the Scsi_Host */
        scsi_host_put(lport->host);
+       module_put(THIS_MODULE);
 }
 
 /**
@@ -848,7 +908,6 @@ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe,
        struct net_device *netdev = fcoe->netdev;
        struct fc_lport *lport = NULL;
        struct fcoe_port *port;
-       struct Scsi_Host *shost;
        int rc;
        /*
         * parent is only a vport if npiv is 1,
@@ -870,7 +929,6 @@ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe,
                rc = -ENOMEM;
                goto out;
        }
-       shost = lport->host;
        port = lport_priv(lport);
        port->lport = lport;
        port->fcoe = fcoe;
@@ -885,7 +943,8 @@ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe,
        }
 
        if (npiv) {
-               FCOE_NETDEV_DBG(netdev, "Setting vport names, 0x%llX 0x%llX\n",
+               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);
@@ -900,7 +959,7 @@ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe,
        }
 
        /* configure lport scsi host properties */
-       rc = fcoe_shost_config(lport, shost, parent);
+       rc = fcoe_shost_config(lport, parent);
        if (rc) {
                FCOE_NETDEV_DBG(netdev, "Could not configure shost for the "
                                "interface\n");
@@ -1024,7 +1083,7 @@ static void fcoe_percpu_thread_destroy(unsigned int cpu)
        struct sk_buff *skb;
 #ifdef CONFIG_SMP
        struct fcoe_percpu_s *p0;
-       unsigned targ_cpu = smp_processor_id();
+       unsigned targ_cpu = get_cpu();
 #endif /* CONFIG_SMP */
 
        FCOE_DBG("Destroying receive thread for CPU %d\n", cpu);
@@ -1080,6 +1139,7 @@ static void fcoe_percpu_thread_destroy(unsigned int cpu)
                        kfree_skb(skb);
                spin_unlock_bh(&p->fcoe_rx_list.lock);
        }
+       put_cpu();
 #else
        /*
         * This a non-SMP scenario where the singular Rx thread is
@@ -1212,7 +1272,7 @@ int fcoe_rcv(struct sk_buff *skb, struct net_device *netdev,
                                "CPU.\n");
 
                spin_unlock_bh(&fps->fcoe_rx_list.lock);
-               cpu = first_cpu(cpu_online_map);
+               cpu = cpumask_first(cpu_online_mask);
                fps = &per_cpu(fcoe_percpu, cpu);
                spin_lock_bh(&fps->fcoe_rx_list.lock);
                if (!fps->thread) {
@@ -1226,16 +1286,30 @@ int fcoe_rcv(struct sk_buff *skb, struct net_device *netdev,
         * this skb. We also have this receive thread locked,
         * so we're free to queue skbs into it's queue.
         */
-       __skb_queue_tail(&fps->fcoe_rx_list, skb);
-       if (fps->fcoe_rx_list.qlen == 1)
-               wake_up_process(fps->thread);
 
-       spin_unlock_bh(&fps->fcoe_rx_list.lock);
+       /* If this is a SCSI-FCP frame, and this is already executing on the
+        * correct CPU, and the queue for this CPU is empty, then go ahead
+        * and process the frame directly in the softirq context.
+        * This lets us process completions without context switching from the
+        * NET_RX softirq, to our receive processing thread, and then back to
+        * BLOCK softirq context.
+        */
+       if (fh->fh_type == FC_TYPE_FCP &&
+           cpu == smp_processor_id() &&
+           skb_queue_empty(&fps->fcoe_rx_list)) {
+               spin_unlock_bh(&fps->fcoe_rx_list.lock);
+               fcoe_recv_frame(skb);
+       } else {
+               __skb_queue_tail(&fps->fcoe_rx_list, skb);
+               if (fps->fcoe_rx_list.qlen == 1)
+                       wake_up_process(fps->thread);
+               spin_unlock_bh(&fps->fcoe_rx_list.lock);
+       }
 
        return 0;
 err:
-       fc_lport_get_stats(lport)->ErrorFrames++;
-
+       per_cpu_ptr(lport->dev_stats, get_cpu())->ErrorFrames++;
+       put_cpu();
 err2:
        kfree_skb(skb);
        return -1;
@@ -1252,10 +1326,11 @@ err2:
  */
 static inline int fcoe_start_io(struct sk_buff *skb)
 {
+       struct sk_buff *nskb;
        int rc;
 
-       skb_get(skb);
-       rc = dev_queue_xmit(skb);
+       nskb = skb_clone(skb, GFP_ATOMIC);
+       rc = dev_queue_xmit(nskb);
        if (rc != 0)
                return rc;
        kfree_skb(skb);
@@ -1380,7 +1455,7 @@ int fcoe_xmit(struct fc_lport *lport, struct fc_frame *fp)
                return 0;
        }
 
-       if (unlikely(fh->fh_r_ctl == FC_RCTL_ELS_REQ) &&
+       if (unlikely(fh->fh_type == FC_TYPE_ELS) &&
            fcoe_ctlr_els_send(&fcoe->ctlr, lport, skb))
                return 0;
 
@@ -1463,9 +1538,10 @@ int fcoe_xmit(struct fc_lport *lport, struct fc_frame *fp)
                skb_shinfo(skb)->gso_size = 0;
        }
        /* update tx stats: regardless if LLD fails */
-       stats = fc_lport_get_stats(lport);
+       stats = per_cpu_ptr(lport->dev_stats, get_cpu());
        stats->TxFrames++;
        stats->TxWords += wlen;
+       put_cpu();
 
        /* send down to lld */
        fr_dev(fp) = lport;
@@ -1487,26 +1563,125 @@ static void fcoe_percpu_flush_done(struct sk_buff *skb)
 }
 
 /**
- * fcoe_percpu_receive_thread() - The per-CPU packet receive thread
- * @arg: The per-CPU context
- *
- * Return: 0 for success
+ * fcoe_recv_frame() - process a single received frame
+ * @skb: frame to process
  */
-int fcoe_percpu_receive_thread(void *arg)
+static void fcoe_recv_frame(struct sk_buff *skb)
 {
-       struct fcoe_percpu_s *p = arg;
        u32 fr_len;
        struct fc_lport *lport;
        struct fcoe_rcv_info *fr;
        struct fcoe_dev_stats *stats;
        struct fc_frame_header *fh;
-       struct sk_buff *skb;
        struct fcoe_crc_eof crc_eof;
        struct fc_frame *fp;
-       u8 *mac = NULL;
        struct fcoe_port *port;
        struct fcoe_hdr *hp;
 
+       fr = fcoe_dev_from_skb(skb);
+       lport = fr->fr_dev;
+       if (unlikely(!lport)) {
+               if (skb->destructor != fcoe_percpu_flush_done)
+                       FCOE_NETDEV_DBG(skb->dev, "NULL lport in skb");
+               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",
+                       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);
+       if (skb_is_nonlinear(skb))
+               skb_linearize(skb);     /* not ideal */
+
+       /*
+        * Frame length checks and setting up the header pointers
+        * was done in fcoe_rcv already.
+        */
+       hp = (struct fcoe_hdr *) skb_network_header(skb);
+       fh = (struct fc_frame_header *) skb_transport_header(skb);
+
+       stats = per_cpu_ptr(lport->dev_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;
+
+       /*
+        * 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 = fc_frame_header_get(fp);
+       if ((fh->fh_r_ctl != FC_RCTL_DD_SOL_DATA ||
+           fh->fh_type != FC_TYPE_FCP) &&
+           (fr_flags(fp) & FCPHF_CRC_UNCHECKED)) {
+               if (le32_to_cpu(fr_crc(fp)) !=
+                   ~crc32(~0, skb->data, fr_len)) {
+                       if (stats->InvalidCRCCount < 5)
+                               printk(KERN_WARNING "fcoe: dropping "
+                                      "frame with CRC error\n");
+                       stats->InvalidCRCCount++;
+                       goto drop;
+               }
+               fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED;
+       }
+       put_cpu();
+       fc_exch_recv(lport, fp);
+       return;
+
+drop:
+       stats->ErrorFrames++;
+       put_cpu();
+       kfree_skb(skb);
+}
+
+/**
+ * fcoe_percpu_receive_thread() - The per-CPU packet receive thread
+ * @arg: The per-CPU context
+ *
+ * Return: 0 for success
+ */
+int fcoe_percpu_receive_thread(void *arg)
+{
+       struct fcoe_percpu_s *p = arg;
+       struct sk_buff *skb;
+
        set_user_nice(current, -20);
 
        while (!kthread_should_stop()) {
@@ -1522,105 +1697,7 @@ int fcoe_percpu_receive_thread(void *arg)
                        spin_lock_bh(&p->fcoe_rx_list.lock);
                }
                spin_unlock_bh(&p->fcoe_rx_list.lock);
-               fr = fcoe_dev_from_skb(skb);
-               lport = fr->fr_dev;
-               if (unlikely(!lport)) {
-                       if (skb->destructor != fcoe_percpu_flush_done)
-                               FCOE_NETDEV_DBG(skb->dev, "NULL lport in skb");
-                       kfree_skb(skb);
-                       continue;
-               }
-
-               FCOE_NETDEV_DBG(skb->dev, "skb_info: len:%d data_len:%d "
-                               "head:%p data:%p tail:%p end:%p sum:%d dev:%s",
-                               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>");
-
-               /*
-                * Save source MAC address before discarding header.
-                */
-               port = lport_priv(lport);
-               if (skb_is_nonlinear(skb))
-                       skb_linearize(skb);     /* not ideal */
-               mac = eth_hdr(skb)->h_source;
-
-               /*
-                * Frame length checks and setting up the header pointers
-                * was done in fcoe_rcv already.
-                */
-               hp = (struct fcoe_hdr *) skb_network_header(skb);
-               fh = (struct fc_frame_header *) skb_transport_header(skb);
-
-               stats = fc_lport_get_stats(lport);
-               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);
-                       stats->ErrorFrames++;
-                       kfree_skb(skb);
-                       continue;
-               }
-
-               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))) {
-                       kfree_skb(skb);
-                       continue;
-               }
-               fr_eof(fp) = crc_eof.fcoe_eof;
-               fr_crc(fp) = crc_eof.fcoe_crc32;
-               if (pskb_trim(skb, fr_len)) {
-                       kfree_skb(skb);
-                       continue;
-               }
-
-               /*
-                * 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 = fc_frame_header_get(fp);
-               if (fh->fh_r_ctl == FC_RCTL_DD_SOL_DATA &&
-                   fh->fh_type == FC_TYPE_FCP) {
-                       fc_exch_recv(lport, fp);
-                       continue;
-               }
-               if (fr_flags(fp) & FCPHF_CRC_UNCHECKED) {
-                       if (le32_to_cpu(fr_crc(fp)) !=
-                           ~crc32(~0, skb->data, fr_len)) {
-                               if (stats->InvalidCRCCount < 5)
-                                       printk(KERN_WARNING "fcoe: dropping "
-                                              "frame with CRC error\n");
-                               stats->InvalidCRCCount++;
-                               stats->ErrorFrames++;
-                               fc_frame_free(fp);
-                               continue;
-                       }
-                       fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED;
-               }
-               fc_exch_recv(lport, fp);
+               fcoe_recv_frame(skb);
        }
        return 0;
 }
@@ -1761,11 +1838,15 @@ static int fcoe_device_notification(struct notifier_block *notifier,
                FCOE_NETDEV_DBG(netdev, "Unknown event %ld "
                                "from netdev netlink\n", event);
        }
+
+       fcoe_link_speed_update(lport);
+
        if (link_possible && !fcoe_link_ok(lport))
                fcoe_ctlr_link_up(&fcoe->ctlr);
        else if (fcoe_ctlr_link_down(&fcoe->ctlr)) {
-               stats = fc_lport_get_stats(lport);
+               stats = per_cpu_ptr(lport->dev_stats, get_cpu());
                stats->LinkFailureCount++;
+               put_cpu();
                fcoe_clean_pending_queue(lport);
        }
 out:
@@ -1794,6 +1875,117 @@ static struct net_device *fcoe_if_to_netdev(const char *buffer)
 }
 
 /**
+ * fcoe_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_disable(const char *buffer, struct kernel_param *kp)
+{
+       struct fcoe_interface *fcoe;
+       struct net_device *netdev;
+       int rc = 0;
+
+       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;
+       }
+
+       if (!rtnl_trylock()) {
+               dev_put(netdev);
+               mutex_unlock(&fcoe_config_mutex);
+               return restart_syscall();
+       }
+
+       fcoe = fcoe_hostlist_lookup_port(netdev);
+       rtnl_unlock();
+
+       if (fcoe) {
+               fc_fabric_logoff(fcoe->ctlr.lp);
+               fcoe_ctlr_link_down(&fcoe->ctlr);
+       } else
+               rc = -ENODEV;
+
+       dev_put(netdev);
+out_nodev:
+       mutex_unlock(&fcoe_config_mutex);
+       return rc;
+}
+
+/**
+ * fcoe_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_enable(const char *buffer, struct kernel_param *kp)
+{
+       struct fcoe_interface *fcoe;
+       struct net_device *netdev;
+       int rc = 0;
+
+       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;
+       }
+
+       if (!rtnl_trylock()) {
+               dev_put(netdev);
+               mutex_unlock(&fcoe_config_mutex);
+               return restart_syscall();
+       }
+
+       fcoe = fcoe_hostlist_lookup_port(netdev);
+       rtnl_unlock();
+
+       if (fcoe) {
+               if (!fcoe_link_ok(fcoe->ctlr.lp))
+                       fcoe_ctlr_link_up(&fcoe->ctlr);
+               rc = fc_fabric_login(fcoe->ctlr.lp);
+       } else
+               rc = -ENODEV;
+
+       dev_put(netdev);
+out_nodev:
+       mutex_unlock(&fcoe_config_mutex);
+       return rc;
+}
+
+/**
  * fcoe_destroy() - Destroy a FCoE interface
  * @buffer: The name of the Ethernet interface to be destroyed
  * @kp:            The associated kernel parameter
@@ -1827,7 +2019,12 @@ static int fcoe_destroy(const char *buffer, struct kernel_param *kp)
                goto out_nodev;
        }
 
-       rtnl_lock();
+       if (!rtnl_trylock()) {
+               dev_put(netdev);
+               mutex_unlock(&fcoe_config_mutex);
+               return restart_syscall();
+       }
+
        fcoe = fcoe_hostlist_lookup_port(netdev);
        if (!fcoe) {
                rtnl_unlock();
@@ -1836,8 +2033,9 @@ static int fcoe_destroy(const char *buffer, struct kernel_param *kp)
        }
        list_del(&fcoe->list);
        fcoe_interface_cleanup(fcoe);
-       rtnl_unlock();
+       /* RTNL mutex is dropped by fcoe_if_destroy */
        fcoe_if_destroy(fcoe->ctlr.lp);
+
 out_putdev:
        dev_put(netdev);
 out_nodev:
@@ -1855,6 +2053,8 @@ static void fcoe_destroy_work(struct work_struct *work)
 
        port = container_of(work, struct fcoe_port, destroy_work);
        mutex_lock(&fcoe_config_mutex);
+       rtnl_lock();
+       /* RTNL mutex is dropped by fcoe_if_destroy */
        fcoe_if_destroy(port->lport);
        mutex_unlock(&fcoe_config_mutex);
 }
@@ -1876,6 +2076,12 @@ static int fcoe_create(const char *buffer, struct kernel_param *kp)
        struct net_device *netdev;
 
        mutex_lock(&fcoe_config_mutex);
+
+       if (!rtnl_trylock()) {
+               mutex_unlock(&fcoe_config_mutex);
+               return restart_syscall();
+       }
+
 #ifdef CONFIG_FCOE_MODULE
        /*
         * Make sure the module has been initialized, and is not about to be
@@ -1884,11 +2090,15 @@ static int fcoe_create(const char *buffer, struct kernel_param *kp)
         */
        if (THIS_MODULE->state != MODULE_STATE_LIVE) {
                rc = -ENODEV;
-               goto out_nodev;
+               goto out_nomod;
        }
 #endif
 
-       rtnl_lock();
+       if (!try_module_get(THIS_MODULE)) {
+               rc = -EINVAL;
+               goto out_nomod;
+       }
+
        netdev = fcoe_if_to_netdev(buffer);
        if (!netdev) {
                rc = -ENODEV;
@@ -1928,43 +2138,42 @@ static int fcoe_create(const char *buffer, struct kernel_param *kp)
        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);
+       dev_put(netdev);
+       rtnl_unlock();
+       mutex_unlock(&fcoe_config_mutex);
+
+       return 0;
+out_free:
+       fcoe_interface_put(fcoe);
 out_putdev:
        dev_put(netdev);
 out_nodev:
+       module_put(THIS_MODULE);
+out_nomod:
        rtnl_unlock();
        mutex_unlock(&fcoe_config_mutex);
        return rc;
 }
 
 /**
- * fcoe_link_ok() - Check if the link is OK for a local port
- * @lport: The local port to check link on
- *
- * Any permanently-disqualifying conditions have been previously checked.
- * This also updates the speed setting, which may change with link for 100/1000.
- *
- * This function should probably be checking for PAUSE support at some point
- * in the future. Currently Per-priority-pause is not determinable using
- * ethtool, so we shouldn't be restrictive until that problem is resolved.
- *
- * Returns: 0 if link is OK for use by FCoE.
+ * 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_ok(struct fc_lport *lport)
+int fcoe_link_speed_update(struct fc_lport *lport)
 {
        struct fcoe_port *port = lport_priv(lport);
        struct net_device *netdev = port->fcoe->netdev;
        struct ethtool_cmd ecmd = { ETHTOOL_GSET };
 
-       if ((netdev->flags & IFF_UP) && netif_carrier_ok(netdev) &&
-           (!dev_ethtool_get_settings(netdev, &ecmd))) {
+       if (!dev_ethtool_get_settings(netdev, &ecmd)) {
                lport->link_supported_speeds &=
                        ~(FC_PORTSPEED_1GBIT | FC_PORTSPEED_10GBIT);
                if (ecmd.supported & (SUPPORTED_1000baseT_Half |
@@ -1984,6 +2193,23 @@ int fcoe_link_ok(struct fc_lport *lport)
 }
 
 /**
+ * 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
+ *
+ */
+int fcoe_link_ok(struct fc_lport *lport)
+{
+       struct fcoe_port *port = lport_priv(lport);
+       struct net_device *netdev = port->fcoe->netdev;
+
+       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
  *
@@ -2242,15 +2468,12 @@ static void fcoe_flogi_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
        mac = fr_cb(fp)->granted_mac;
        if (is_zero_ether_addr(mac)) {
                /* pre-FIP */
-               mac = eth_hdr(&fp->skb)->h_source;
-               if (fcoe_ctlr_recv_flogi(fip, lport, fp, mac)) {
+               if (fcoe_ctlr_recv_flogi(fip, lport, fp)) {
                        fc_frame_free(fp);
                        return;
                }
-       } else {
-               /* FIP, libfcoe has already seen it */
-               fip->update_mac(lport, fr_cb(fp)->granted_mac);
        }
+       fcoe_update_src_mac(lport, mac);
 done:
        fc_lport_flogi_resp(seq, fp, lport);
 }
@@ -2266,13 +2489,11 @@ done:
  */
 static void fcoe_logo_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;
+       struct fc_lport *lport = arg;
        static u8 zero_mac[ETH_ALEN] = { 0 };
 
        if (!IS_ERR(fp))
-               fip->update_mac(lport, zero_mac);
+               fcoe_update_src_mac(lport, zero_mac);
        fc_lport_logo_resp(seq, fp, lport);
 }
 
@@ -2307,7 +2528,7 @@ static struct fc_seq *fcoe_elsct_send(struct fc_lport *lport, u32 did,
                if (ntoh24(fh->fh_d_id) != FC_FID_FLOGI)
                        break;
                return fc_elsct_send(lport, did, fp, op, fcoe_logo_resp,
-                                    fip, timeout);
+                                    lport, timeout);
        }
        return fc_elsct_send(lport, did, fp, op, resp, arg, timeout);
 }
@@ -2417,5 +2638,58 @@ static void fcoe_set_vport_symbolic_name(struct fc_vport *vport)
        if (!fp)
                return;
        lport->tt.elsct_send(lport, FC_FID_DIR_SERV, fp, FC_NS_RSPN_ID,
-                            NULL, NULL, lport->e_d_tov);
+                            NULL, NULL, 3 * lport->r_a_tov);
+}
+
+/**
+ * fcoe_get_lesb() - Fill the FCoE Link Error Status Block
+ * @lport: the local port
+ * @fc_lesb: the link error status block
+ */
+static void fcoe_get_lesb(struct fc_lport *lport,
+                        struct fc_els_lesb *fc_lesb)
+{
+       unsigned int cpu;
+       u32 lfc, vlfc, mdac;
+       struct fcoe_dev_stats *devst;
+       struct fcoe_fc_els_lesb *lesb;
+       struct net_device *netdev = fcoe_netdev(lport);
+
+       lfc = 0;
+       vlfc = 0;
+       mdac = 0;
+       lesb = (struct fcoe_fc_els_lesb *)fc_lesb;
+       memset(lesb, 0, sizeof(*lesb));
+       for_each_possible_cpu(cpu) {
+               devst = per_cpu_ptr(lport->dev_stats, cpu);
+               lfc += devst->LinkFailureCount;
+               vlfc += devst->VLinkFailureCount;
+               mdac += devst->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)->rx_crc_errors);
+}
+
+/**
+ * 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->fcoe;
+
+       if (fp && fc_frame_payload_op(fp) == ELS_FLOGI)
+               fcoe_ctlr_recv_flogi(&fcoe->ctlr, lport, fp);
 }