]> Pileus Git - ~andy/linux/blobdiff - drivers/scsi/fnic/fnic_fcs.c
Merge tag 'trace-3.12' of git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux...
[~andy/linux] / drivers / scsi / fnic / fnic_fcs.c
index 483eb9dbe66366377c5dd4f049c15a160ca2a500..006fa92a02df0b4178f2ce88c73dffd8635cb5fc 100644 (file)
 #include <scsi/libfc.h>
 #include "fnic_io.h"
 #include "fnic.h"
+#include "fnic_fip.h"
 #include "cq_enet_desc.h"
 #include "cq_exch_desc.h"
 
+static u8 fcoe_all_fcfs[ETH_ALEN];
+struct workqueue_struct *fnic_fip_queue;
 struct workqueue_struct *fnic_event_queue;
 
 static void fnic_set_eth_mode(struct fnic *);
+static void fnic_fcoe_send_vlan_req(struct fnic *fnic);
+static void fnic_fcoe_start_fcf_disc(struct fnic *fnic);
+static void fnic_fcoe_process_vlan_resp(struct fnic *fnic, struct sk_buff *);
+static int fnic_fcoe_vlan_check(struct fnic *fnic, u16 flag);
+static int fnic_fcoe_handle_fip_frame(struct fnic *fnic, struct sk_buff *skb);
 
 void fnic_handle_link(struct work_struct *work)
 {
@@ -69,6 +77,11 @@ void fnic_handle_link(struct work_struct *work)
                                FNIC_FCS_DBG(KERN_DEBUG, fnic->lport->host,
                                             "link down\n");
                                fcoe_ctlr_link_down(&fnic->ctlr);
+                               if (fnic->config.flags & VFCF_FIP_CAPABLE) {
+                                       /* start FCoE VLAN discovery */
+                                       fnic_fcoe_send_vlan_req(fnic);
+                                       return;
+                               }
                                FNIC_FCS_DBG(KERN_DEBUG, fnic->lport->host,
                                             "link up\n");
                                fcoe_ctlr_link_up(&fnic->ctlr);
@@ -79,6 +92,11 @@ void fnic_handle_link(struct work_struct *work)
        } else if (fnic->link_status) {
                /* DOWN -> UP */
                spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+               if (fnic->config.flags & VFCF_FIP_CAPABLE) {
+                       /* start FCoE VLAN discovery */
+                       fnic_fcoe_send_vlan_req(fnic);
+                       return;
+               }
                FNIC_FCS_DBG(KERN_DEBUG, fnic->lport->host, "link up\n");
                fcoe_ctlr_link_up(&fnic->ctlr);
        } else {
@@ -128,6 +146,441 @@ void fnic_handle_frame(struct work_struct *work)
        }
 }
 
+void fnic_fcoe_evlist_free(struct fnic *fnic)
+{
+       struct fnic_event *fevt = NULL;
+       struct fnic_event *next = NULL;
+       unsigned long flags;
+
+       spin_lock_irqsave(&fnic->fnic_lock, flags);
+       if (list_empty(&fnic->evlist)) {
+               spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+               return;
+       }
+
+       list_for_each_entry_safe(fevt, next, &fnic->evlist, list) {
+               list_del(&fevt->list);
+               kfree(fevt);
+       }
+       spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+}
+
+void fnic_handle_event(struct work_struct *work)
+{
+       struct fnic *fnic = container_of(work, struct fnic, event_work);
+       struct fnic_event *fevt = NULL;
+       struct fnic_event *next = NULL;
+       unsigned long flags;
+
+       spin_lock_irqsave(&fnic->fnic_lock, flags);
+       if (list_empty(&fnic->evlist)) {
+               spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+               return;
+       }
+
+       list_for_each_entry_safe(fevt, next, &fnic->evlist, list) {
+               if (fnic->stop_rx_link_events) {
+                       list_del(&fevt->list);
+                       kfree(fevt);
+                       spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+                       return;
+               }
+               /*
+                * If we're in a transitional state, just re-queue and return.
+                * The queue will be serviced when we get to a stable state.
+                */
+               if (fnic->state != FNIC_IN_FC_MODE &&
+                   fnic->state != FNIC_IN_ETH_MODE) {
+                       spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+                       return;
+               }
+
+               list_del(&fevt->list);
+               switch (fevt->event) {
+               case FNIC_EVT_START_VLAN_DISC:
+                       spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+                       fnic_fcoe_send_vlan_req(fnic);
+                       spin_lock_irqsave(&fnic->fnic_lock, flags);
+                       break;
+               case FNIC_EVT_START_FCF_DISC:
+                       FNIC_FCS_DBG(KERN_DEBUG, fnic->lport->host,
+                                 "Start FCF Discovery\n");
+                       fnic_fcoe_start_fcf_disc(fnic);
+                       break;
+               default:
+                       FNIC_FCS_DBG(KERN_DEBUG, fnic->lport->host,
+                                 "Unknown event 0x%x\n", fevt->event);
+                       break;
+               }
+               kfree(fevt);
+       }
+       spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+}
+
+/**
+ * Check if the Received FIP FLOGI frame is rejected
+ * @fip: The FCoE controller that received the frame
+ * @skb: The received FIP frame
+ *
+ * Returns non-zero if the frame is rejected with unsupported cmd with
+ * insufficient resource els explanation.
+ */
+static inline int is_fnic_fip_flogi_reject(struct fcoe_ctlr *fip,
+                                        struct sk_buff *skb)
+{
+       struct fc_lport *lport = fip->lp;
+       struct fip_header *fiph;
+       struct fc_frame_header *fh = NULL;
+       struct fip_desc *desc;
+       struct fip_encaps *els;
+       enum fip_desc_type els_dtype = 0;
+       u16 op;
+       u8 els_op;
+       u8 sub;
+
+       size_t els_len = 0;
+       size_t rlen;
+       size_t dlen = 0;
+
+       if (skb_linearize(skb))
+               return 0;
+
+       if (skb->len < sizeof(*fiph))
+               return 0;
+
+       fiph = (struct fip_header *)skb->data;
+       op = ntohs(fiph->fip_op);
+       sub = fiph->fip_subcode;
+
+       if (op != FIP_OP_LS)
+               return 0;
+
+       if (sub != FIP_SC_REP)
+               return 0;
+
+       rlen = ntohs(fiph->fip_dl_len) * 4;
+       if (rlen + sizeof(*fiph) > skb->len)
+               return 0;
+
+       desc = (struct fip_desc *)(fiph + 1);
+       dlen = desc->fip_dlen * FIP_BPW;
+
+       if (desc->fip_dtype == FIP_DT_FLOGI) {
+
+               shost_printk(KERN_DEBUG, lport->host,
+                         " FIP TYPE FLOGI: fab name:%llx "
+                         "vfid:%d map:%x\n",
+                         fip->sel_fcf->fabric_name, fip->sel_fcf->vfid,
+                         fip->sel_fcf->fc_map);
+               if (dlen < sizeof(*els) + sizeof(*fh) + 1)
+                       return 0;
+
+               els_len = dlen - sizeof(*els);
+               els = (struct fip_encaps *)desc;
+               fh = (struct fc_frame_header *)(els + 1);
+               els_dtype = desc->fip_dtype;
+
+               if (!fh)
+                       return 0;
+
+               /*
+                * ELS command code, reason and explanation should be = Reject,
+                * unsupported command and insufficient resource
+                */
+               els_op = *(u8 *)(fh + 1);
+               if (els_op == ELS_LS_RJT) {
+                       shost_printk(KERN_INFO, lport->host,
+                                 "Flogi Request Rejected by Switch\n");
+                       return 1;
+               }
+               shost_printk(KERN_INFO, lport->host,
+                               "Flogi Request Accepted by Switch\n");
+       }
+       return 0;
+}
+
+static void fnic_fcoe_send_vlan_req(struct fnic *fnic)
+{
+       struct fcoe_ctlr *fip = &fnic->ctlr;
+       struct sk_buff *skb;
+       char *eth_fr;
+       int fr_len;
+       struct fip_vlan *vlan;
+       u64 vlan_tov;
+
+       fnic_fcoe_reset_vlans(fnic);
+       fnic->set_vlan(fnic, 0);
+       FNIC_FCS_DBG(KERN_INFO, fnic->lport->host,
+                 "Sending VLAN request...\n");
+       skb = dev_alloc_skb(sizeof(struct fip_vlan));
+       if (!skb)
+               return;
+
+       fr_len = sizeof(*vlan);
+       eth_fr = (char *)skb->data;
+       vlan = (struct fip_vlan *)eth_fr;
+
+       memset(vlan, 0, sizeof(*vlan));
+       memcpy(vlan->eth.h_source, fip->ctl_src_addr, ETH_ALEN);
+       memcpy(vlan->eth.h_dest, fcoe_all_fcfs, ETH_ALEN);
+       vlan->eth.h_proto = htons(ETH_P_FIP);
+
+       vlan->fip.fip_ver = FIP_VER_ENCAPS(FIP_VER);
+       vlan->fip.fip_op = htons(FIP_OP_VLAN);
+       vlan->fip.fip_subcode = FIP_SC_VL_REQ;
+       vlan->fip.fip_dl_len = htons(sizeof(vlan->desc) / FIP_BPW);
+
+       vlan->desc.mac.fd_desc.fip_dtype = FIP_DT_MAC;
+       vlan->desc.mac.fd_desc.fip_dlen = sizeof(vlan->desc.mac) / FIP_BPW;
+       memcpy(&vlan->desc.mac.fd_mac, fip->ctl_src_addr, ETH_ALEN);
+
+       vlan->desc.wwnn.fd_desc.fip_dtype = FIP_DT_NAME;
+       vlan->desc.wwnn.fd_desc.fip_dlen = sizeof(vlan->desc.wwnn) / FIP_BPW;
+       put_unaligned_be64(fip->lp->wwnn, &vlan->desc.wwnn.fd_wwn);
+
+       skb_put(skb, sizeof(*vlan));
+       skb->protocol = htons(ETH_P_FIP);
+       skb_reset_mac_header(skb);
+       skb_reset_network_header(skb);
+       fip->send(fip, skb);
+
+       /* set a timer so that we can retry if there no response */
+       vlan_tov = jiffies + msecs_to_jiffies(FCOE_CTLR_FIPVLAN_TOV);
+       mod_timer(&fnic->fip_timer, round_jiffies(vlan_tov));
+}
+
+static void fnic_fcoe_process_vlan_resp(struct fnic *fnic, struct sk_buff *skb)
+{
+       struct fcoe_ctlr *fip = &fnic->ctlr;
+       struct fip_header *fiph;
+       struct fip_desc *desc;
+       u16 vid;
+       size_t rlen;
+       size_t dlen;
+       struct fcoe_vlan *vlan;
+       u64 sol_time;
+       unsigned long flags;
+
+       FNIC_FCS_DBG(KERN_INFO, fnic->lport->host,
+                 "Received VLAN response...\n");
+
+       fiph = (struct fip_header *) skb->data;
+
+       FNIC_FCS_DBG(KERN_INFO, fnic->lport->host,
+                 "Received VLAN response... OP 0x%x SUB_OP 0x%x\n",
+                 ntohs(fiph->fip_op), fiph->fip_subcode);
+
+       rlen = ntohs(fiph->fip_dl_len) * 4;
+       fnic_fcoe_reset_vlans(fnic);
+       spin_lock_irqsave(&fnic->vlans_lock, flags);
+       desc = (struct fip_desc *)(fiph + 1);
+       while (rlen > 0) {
+               dlen = desc->fip_dlen * FIP_BPW;
+               switch (desc->fip_dtype) {
+               case FIP_DT_VLAN:
+                       vid = ntohs(((struct fip_vlan_desc *)desc)->fd_vlan);
+                       shost_printk(KERN_INFO, fnic->lport->host,
+                                 "process_vlan_resp: FIP VLAN %d\n", vid);
+                       vlan = kmalloc(sizeof(*vlan),
+                                                       GFP_ATOMIC);
+                       if (!vlan) {
+                               /* retry from timer */
+                               spin_unlock_irqrestore(&fnic->vlans_lock,
+                                                       flags);
+                               goto out;
+                       }
+                       memset(vlan, 0, sizeof(struct fcoe_vlan));
+                       vlan->vid = vid & 0x0fff;
+                       vlan->state = FIP_VLAN_AVAIL;
+                       list_add_tail(&vlan->list, &fnic->vlans);
+                       break;
+               }
+               desc = (struct fip_desc *)((char *)desc + dlen);
+               rlen -= dlen;
+       }
+
+       /* any VLAN descriptors present ? */
+       if (list_empty(&fnic->vlans)) {
+               /* retry from timer */
+               FNIC_FCS_DBG(KERN_INFO, fnic->lport->host,
+                         "No VLAN descriptors in FIP VLAN response\n");
+               spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+               goto out;
+       }
+
+       vlan = list_first_entry(&fnic->vlans, struct fcoe_vlan, list);
+       fnic->set_vlan(fnic, vlan->vid);
+       vlan->state = FIP_VLAN_SENT; /* sent now */
+       vlan->sol_count++;
+       spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+
+       /* start the solicitation */
+       fcoe_ctlr_link_up(fip);
+
+       sol_time = jiffies + msecs_to_jiffies(FCOE_CTLR_START_DELAY);
+       mod_timer(&fnic->fip_timer, round_jiffies(sol_time));
+out:
+       return;
+}
+
+static void fnic_fcoe_start_fcf_disc(struct fnic *fnic)
+{
+       unsigned long flags;
+       struct fcoe_vlan *vlan;
+       u64 sol_time;
+
+       spin_lock_irqsave(&fnic->vlans_lock, flags);
+       vlan = list_first_entry(&fnic->vlans, struct fcoe_vlan, list);
+       fnic->set_vlan(fnic, vlan->vid);
+       vlan->state = FIP_VLAN_SENT; /* sent now */
+       vlan->sol_count = 1;
+       spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+
+       /* start the solicitation */
+       fcoe_ctlr_link_up(&fnic->ctlr);
+
+       sol_time = jiffies + msecs_to_jiffies(FCOE_CTLR_START_DELAY);
+       mod_timer(&fnic->fip_timer, round_jiffies(sol_time));
+}
+
+static int fnic_fcoe_vlan_check(struct fnic *fnic, u16 flag)
+{
+       unsigned long flags;
+       struct fcoe_vlan *fvlan;
+
+       spin_lock_irqsave(&fnic->vlans_lock, flags);
+       if (list_empty(&fnic->vlans)) {
+               spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+               return -EINVAL;
+       }
+
+       fvlan = list_first_entry(&fnic->vlans, struct fcoe_vlan, list);
+       if (fvlan->state == FIP_VLAN_USED) {
+               spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+               return 0;
+       }
+
+       if (fvlan->state == FIP_VLAN_SENT) {
+               fvlan->state = FIP_VLAN_USED;
+               spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+               return 0;
+       }
+       spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+       return -EINVAL;
+}
+
+static void fnic_event_enq(struct fnic *fnic, enum fnic_evt ev)
+{
+       struct fnic_event *fevt;
+       unsigned long flags;
+
+       fevt = kmalloc(sizeof(*fevt), GFP_ATOMIC);
+       if (!fevt)
+               return;
+
+       fevt->fnic = fnic;
+       fevt->event = ev;
+
+       spin_lock_irqsave(&fnic->fnic_lock, flags);
+       list_add_tail(&fevt->list, &fnic->evlist);
+       spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+
+       schedule_work(&fnic->event_work);
+}
+
+static int fnic_fcoe_handle_fip_frame(struct fnic *fnic, struct sk_buff *skb)
+{
+       struct fip_header *fiph;
+       int ret = 1;
+       u16 op;
+       u8 sub;
+
+       if (!skb || !(skb->data))
+               return -1;
+
+       if (skb_linearize(skb))
+               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;
+
+       if (op == FIP_OP_DISC && sub == FIP_SC_ADV) {
+               if (fnic_fcoe_vlan_check(fnic, ntohs(fiph->fip_flags)))
+                       goto drop;
+               /* pass it on to fcoe */
+               ret = 1;
+       } else if (op == FIP_OP_VLAN && sub == FIP_SC_VL_REP) {
+               /* set the vlan as used */
+               fnic_fcoe_process_vlan_resp(fnic, skb);
+               ret = 0;
+       } else if (op == FIP_OP_CTRL && sub == FIP_SC_CLR_VLINK) {
+               /* received CVL request, restart vlan disc */
+               fnic_event_enq(fnic, FNIC_EVT_START_VLAN_DISC);
+               /* pass it on to fcoe */
+               ret = 1;
+       }
+drop:
+       return ret;
+}
+
+void fnic_handle_fip_frame(struct work_struct *work)
+{
+       struct fnic *fnic = container_of(work, struct fnic, fip_frame_work);
+       unsigned long flags;
+       struct sk_buff *skb;
+       struct ethhdr *eh;
+
+       while ((skb = skb_dequeue(&fnic->fip_frame_queue))) {
+               spin_lock_irqsave(&fnic->fnic_lock, flags);
+               if (fnic->stop_rx_link_events) {
+                       spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+                       dev_kfree_skb(skb);
+                       return;
+               }
+               /*
+                * If we're in a transitional state, just re-queue and return.
+                * The queue will be serviced when we get to a stable state.
+                */
+               if (fnic->state != FNIC_IN_FC_MODE &&
+                   fnic->state != FNIC_IN_ETH_MODE) {
+                       skb_queue_head(&fnic->fip_frame_queue, skb);
+                       spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+                       return;
+               }
+               spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+               eh = (struct ethhdr *)skb->data;
+               if (eh->h_proto == htons(ETH_P_FIP)) {
+                       skb_pull(skb, sizeof(*eh));
+                       if (fnic_fcoe_handle_fip_frame(fnic, skb) <= 0) {
+                               dev_kfree_skb(skb);
+                               continue;
+                       }
+                       /*
+                        * If there's FLOGI rejects - clear all
+                        * fcf's & restart from scratch
+                        */
+                       if (is_fnic_fip_flogi_reject(&fnic->ctlr, skb)) {
+                               shost_printk(KERN_INFO, fnic->lport->host,
+                                         "Trigger a Link down - VLAN Disc\n");
+                               fcoe_ctlr_link_down(&fnic->ctlr);
+                               /* start FCoE VLAN discovery */
+                               fnic_fcoe_send_vlan_req(fnic);
+                               dev_kfree_skb(skb);
+                               continue;
+                       }
+                       fcoe_ctlr_recv(&fnic->ctlr, skb);
+                       continue;
+               }
+       }
+}
+
 /**
  * fnic_import_rq_eth_pkt() - handle received FCoE or FIP frame.
  * @fnic:      fnic instance.
@@ -150,8 +603,14 @@ static inline int fnic_import_rq_eth_pkt(struct fnic *fnic, struct sk_buff *skb)
                skb_reset_mac_header(skb);
        }
        if (eh->h_proto == htons(ETH_P_FIP)) {
-               skb_pull(skb, sizeof(*eh));
-               fcoe_ctlr_recv(&fnic->ctlr, skb);
+               if (!(fnic->config.flags & VFCF_FIP_CAPABLE)) {
+                       printk(KERN_ERR "Dropped FIP frame, as firmware "
+                                       "uses non-FIP mode, Enable FIP "
+                                       "using UCSM\n");
+                       goto drop;
+               }
+               skb_queue_tail(&fnic->fip_frame_queue, skb);
+               queue_work(fnic_fip_queue, &fnic->fip_frame_work);
                return 1;               /* let caller know packet was used */
        }
        if (eh->h_proto != htons(ETH_P_FCOE))
@@ -720,3 +1179,104 @@ void fnic_free_wq_buf(struct vnic_wq *wq, struct vnic_wq_buf *buf)
        dev_kfree_skb(fp_skb(fp));
        buf->os_buf = NULL;
 }
+
+void fnic_fcoe_reset_vlans(struct fnic *fnic)
+{
+       unsigned long flags;
+       struct fcoe_vlan *vlan;
+       struct fcoe_vlan *next;
+
+       /*
+        * indicate a link down to fcoe so that all fcf's are free'd
+        * might not be required since we did this before sending vlan
+        * discovery request
+        */
+       spin_lock_irqsave(&fnic->vlans_lock, flags);
+       if (!list_empty(&fnic->vlans)) {
+               list_for_each_entry_safe(vlan, next, &fnic->vlans, list) {
+                       list_del(&vlan->list);
+                       kfree(vlan);
+               }
+       }
+       spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+}
+
+void fnic_handle_fip_timer(struct fnic *fnic)
+{
+       unsigned long flags;
+       struct fcoe_vlan *vlan;
+       u64 sol_time;
+
+       spin_lock_irqsave(&fnic->fnic_lock, flags);
+       if (fnic->stop_rx_link_events) {
+               spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+               return;
+       }
+       spin_unlock_irqrestore(&fnic->fnic_lock, flags);
+
+       if (fnic->ctlr.mode == FIP_ST_NON_FIP)
+               return;
+
+       spin_lock_irqsave(&fnic->vlans_lock, flags);
+       if (list_empty(&fnic->vlans)) {
+               /* no vlans available, try again */
+               FNIC_FCS_DBG(KERN_DEBUG, fnic->lport->host,
+                         "Start VLAN Discovery\n");
+               spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+               fnic_event_enq(fnic, FNIC_EVT_START_VLAN_DISC);
+               return;
+       }
+
+       vlan = list_first_entry(&fnic->vlans, struct fcoe_vlan, list);
+       shost_printk(KERN_DEBUG, fnic->lport->host,
+                 "fip_timer: vlan %d state %d sol_count %d\n",
+                 vlan->vid, vlan->state, vlan->sol_count);
+       switch (vlan->state) {
+       case FIP_VLAN_USED:
+               FNIC_FCS_DBG(KERN_DEBUG, fnic->lport->host,
+                         "FIP VLAN is selected for FC transaction\n");
+               spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+               break;
+       case FIP_VLAN_FAILED:
+               /* if all vlans are in failed state, restart vlan disc */
+               FNIC_FCS_DBG(KERN_DEBUG, fnic->lport->host,
+                         "Start VLAN Discovery\n");
+               spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+               fnic_event_enq(fnic, FNIC_EVT_START_VLAN_DISC);
+               break;
+       case FIP_VLAN_SENT:
+               if (vlan->sol_count >= FCOE_CTLR_MAX_SOL) {
+                       /*
+                        * no response on this vlan, remove  from the list.
+                        * Try the next vlan
+                        */
+                       shost_printk(KERN_INFO, fnic->lport->host,
+                                 "Dequeue this VLAN ID %d from list\n",
+                                 vlan->vid);
+                       list_del(&vlan->list);
+                       kfree(vlan);
+                       vlan = NULL;
+                       if (list_empty(&fnic->vlans)) {
+                               /* we exhausted all vlans, restart vlan disc */
+                               spin_unlock_irqrestore(&fnic->vlans_lock,
+                                                       flags);
+                               shost_printk(KERN_INFO, fnic->lport->host,
+                                         "fip_timer: vlan list empty, "
+                                         "trigger vlan disc\n");
+                               fnic_event_enq(fnic, FNIC_EVT_START_VLAN_DISC);
+                               return;
+                       }
+                       /* check the next vlan */
+                       vlan = list_first_entry(&fnic->vlans, struct fcoe_vlan,
+                                                       list);
+                       fnic->set_vlan(fnic, vlan->vid);
+                       vlan->state = FIP_VLAN_SENT; /* sent now */
+               }
+               spin_unlock_irqrestore(&fnic->vlans_lock, flags);
+               vlan->sol_count++;
+               sol_time = jiffies + msecs_to_jiffies
+                                       (FCOE_CTLR_START_DELAY);
+               mod_timer(&fnic->fip_timer, round_jiffies(sol_time));
+               break;
+       }
+}