|
@@ -31,12 +31,20 @@
|
|
|
#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,8 @@ 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);
|
|
|
+ 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 +1173,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;
|
|
|
+ }
|
|
|
+}
|