summaryrefslogblamecommitdiff
path: root/drivers/scsi/fnic/fip.c
blob: 7bb85949033fee30952353e389f9dcac8201c895 (plain) (tree)
1
2
3
4
5
6
7
8
9







                                                           


















                                                                              
                                                           
















                                                                  
                                                                  





                                                                  
                                                           
                                        
                                                           





















                                                                       
                                                           



                                                                     
                                                           





















                                                                             
                                                           

                                                           
                                                           










                                                                             
                                                                   





                                                                                                
                                                           




                                                                              
                                                                   








                                                                         
                 






                                                                              





                                                                         
                                                                   






















                                                                                    
                                                                  
























                                                                                               
                                                           
































                                                                             
                                                           





                                                                                       
                                                                   






                                                                                      
                                                                   

















                                                                                              
                                                                

































































                                                                                                 
                                                                  


















































                                                                                                  
                                                                   









                                                                          
                                                           



























                                                                             
                                                           

                                                          
                                                                   







                                                                                  
                                                                   










                                                                          
                                                                   





                                                                                         
                                                                   







                                                                              
                                                           














                                                                                         
                                                                   






                                                                                     
                                                           
                                                                         
                                                    









































                                                                                         
                                                           





































                                                                        
                          
 
                                                           





                                                                     
                                                                   












                                                                                      
                                                                   














                                                                                           





                                                                                   
                                                                                   


                                                                                                 
                                                                           





                                                                                       



































                                                                      
                                                           







                                                                            
                                                           





                                                                        
                                                                   

                                                                               
                                                                   
                                                                    
                         












                                                                                         
                                                           



                                                                                
                                                                   












































                                                                        
                                                                  


















                                                                                 
                                                            






























                                                                      
                                                                  























                                                                                       
                                                            























































































                                                                                
                                                           


                                                              
                                   


















                                                                    
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright 2008 Cisco Systems, Inc.  All rights reserved.
 * Copyright 2007 Nuova Systems, Inc.  All rights reserved.
 */
#include "fnic.h"
#include "fip.h"
#include <linux/etherdevice.h>

#define FIP_FNIC_RESET_WAIT_COUNT 15

/**
 * fnic_fcoe_reset_vlans - Free up the list of discovered vlans
 * @fnic: Handle to fnic driver instance
 */
void fnic_fcoe_reset_vlans(struct fnic *fnic)
{
	unsigned long flags;
	struct fcoe_vlan *vlan, *next;

	spin_lock_irqsave(&fnic->vlans_lock, flags);
	if (!list_empty(&fnic->vlan_list)) {
		list_for_each_entry_safe(vlan, next, &fnic->vlan_list, list) {
			list_del(&vlan->list);
			kfree(vlan);
		}
	}

	spin_unlock_irqrestore(&fnic->vlans_lock, flags);
	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "Reset vlan complete\n");
}

/**
 * fnic_fcoe_send_vlan_req - Send FIP vlan request to all FCFs MAC
 * @fnic: Handle to fnic driver instance
 */
void fnic_fcoe_send_vlan_req(struct fnic *fnic)
{
	uint8_t *frame;
	struct fnic_iport_s *iport = &fnic->iport;
	struct fnic_stats *fnic_stats = &fnic->fnic_stats;
	u64 vlan_tov;
	struct fip_vlan_req *pvlan_req;
	uint16_t frame_size = sizeof(struct fip_vlan_req);

	frame = fdls_alloc_frame(iport);
	if (frame == NULL) {
		FNIC_FIP_DBG(KERN_ERR, fnic->host, fnic->fnic_num,
		     "Failed to allocate frame to send VLAN req");
		return;
	}

	fnic_fcoe_reset_vlans(fnic);

	fnic->set_vlan(fnic, 0);
	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "set vlan done\n");

	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "got MAC 0x%x:%x:%x:%x:%x:%x\n", iport->hwmac[0],
		     iport->hwmac[1], iport->hwmac[2], iport->hwmac[3],
		     iport->hwmac[4], iport->hwmac[5]);

	pvlan_req = (struct fip_vlan_req *) frame;
	*pvlan_req = (struct fip_vlan_req) {
		.eth = {.h_dest = FCOE_ALL_FCFS_MAC,
			.h_proto = cpu_to_be16(ETH_P_FIP)},
		.fip = {.fip_ver = FIP_VER_ENCAPS(FIP_VER),
			.fip_op = cpu_to_be16(FIP_OP_VLAN),
			.fip_subcode = FIP_SC_REQ,
			.fip_dl_len = cpu_to_be16(FIP_VLAN_REQ_LEN)},
		.mac_desc = {.fd_desc = {.fip_dtype = FIP_DT_MAC,
						.fip_dlen = 2}}
	};

	memcpy(pvlan_req->eth.h_source, iport->hwmac, ETH_ALEN);
	memcpy(pvlan_req->mac_desc.fd_mac, iport->hwmac, ETH_ALEN);

	atomic64_inc(&fnic_stats->vlan_stats.vlan_disc_reqs);

	iport->fip.state = FDLS_FIP_VLAN_DISCOVERY_STARTED;

	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "Send VLAN req\n");
	fnic_send_fip_frame(iport, frame, frame_size);

	vlan_tov = jiffies + msecs_to_jiffies(FCOE_CTLR_FIPVLAN_TOV);
	mod_timer(&fnic->retry_fip_timer, round_jiffies(vlan_tov));
	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "fip timer set\n");
}

/**
 * fnic_fcoe_process_vlan_resp - Processes the vlan response from one FCF and
 * populates VLAN list.
 * @fnic: Handle to fnic driver instance
 * @fiph: Received FIP frame
 *
 * Will wait for responses from multiple FCFs until timeout.
 */
void fnic_fcoe_process_vlan_resp(struct fnic *fnic, struct fip_header *fiph)
{
	struct fip_vlan_notif *vlan_notif = (struct fip_vlan_notif *)fiph;

	struct fnic_stats *fnic_stats = &fnic->fnic_stats;
	u16 vid;
	int num_vlan = 0;
	int cur_desc, desc_len;
	struct fcoe_vlan *vlan;
	struct fip_vlan_desc *vlan_desc;
	unsigned long flags;

	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "fnic 0x%p got vlan resp\n", fnic);

	desc_len = be16_to_cpu(vlan_notif->fip.fip_dl_len);
	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "desc_len %d\n", desc_len);

	spin_lock_irqsave(&fnic->vlans_lock, flags);

	cur_desc = 0;
	while (desc_len > 0) {
		vlan_desc =
		    (struct fip_vlan_desc *)(((char *)vlan_notif->vlans_desc)
					       + cur_desc * 4);

		if (vlan_desc->fd_desc.fip_dtype == FIP_DT_VLAN) {
			if (vlan_desc->fd_desc.fip_dlen != 1) {
				FNIC_FIP_DBG(KERN_INFO, fnic->host,
					     fnic->fnic_num,
					     "Invalid descriptor length(%x) in VLan response\n",
					     vlan_desc->fd_desc.fip_dlen);

			}
			num_vlan++;
			vid = be16_to_cpu(vlan_desc->fd_vlan);
			FNIC_FIP_DBG(KERN_INFO, fnic->host,
				     fnic->fnic_num,
				     "process_vlan_resp: FIP VLAN %d\n", vid);
			vlan = kzalloc(sizeof(*vlan), GFP_KERNEL);

			if (!vlan) {
				/* retry from timer */
				FNIC_FIP_DBG(KERN_INFO, fnic->host,
					     fnic->fnic_num,
					     "Mem Alloc failure\n");
				spin_unlock_irqrestore(&fnic->vlans_lock,
						       flags);
				goto out;
			}
			vlan->vid = vid & 0x0fff;
			vlan->state = FIP_VLAN_AVAIL;
			list_add_tail(&vlan->list, &fnic->vlan_list);
			break;
		}
		FNIC_FIP_DBG(KERN_INFO, fnic->host,
			     fnic->fnic_num,
			     "Invalid descriptor type(%x) in VLan response\n",
			     vlan_desc->fd_desc.fip_dtype);
		/*
		 * Note : received a type=2 descriptor here i.e. FIP
		 * MAC Address Descriptor
		 */
		cur_desc += vlan_desc->fd_desc.fip_dlen;
		desc_len -= vlan_desc->fd_desc.fip_dlen;
	}

	/* any VLAN descriptors present ? */
	if (num_vlan == 0) {
		atomic64_inc(&fnic_stats->vlan_stats.resp_withno_vlanID);
		FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
			     "fnic 0x%p No VLAN descriptors in FIP VLAN response\n",
			     fnic);
	}

	spin_unlock_irqrestore(&fnic->vlans_lock, flags);

 out:
	return;
}

/**
 * fnic_fcoe_start_fcf_discovery - Start FIP FCF discovery in a selected vlan
 * @fnic: Handle to fnic driver instance
 */
void fnic_fcoe_start_fcf_discovery(struct fnic *fnic)
{
	uint8_t *frame;
	struct fnic_iport_s *iport = &fnic->iport;
	u64 fcs_tov;
	struct fip_discovery *pdisc_sol;
	uint16_t frame_size = sizeof(struct fip_discovery);

	frame = fdls_alloc_frame(iport);
	if (frame == NULL) {
		FNIC_FIP_DBG(KERN_ERR, fnic->host, fnic->fnic_num,
		     "Failed to allocate frame to start FCF discovery");
		return;
	}

	memset(iport->selected_fcf.fcf_mac, 0, ETH_ALEN);

	pdisc_sol = (struct fip_discovery *) frame;
	*pdisc_sol = (struct fip_discovery) {
		.eth = {.h_dest = FCOE_ALL_FCFS_MAC,
			.h_proto = cpu_to_be16(ETH_P_FIP)},
		.fip = {
			.fip_ver = FIP_VER_ENCAPS(FIP_VER), .fip_op = cpu_to_be16(FIP_OP_DISC),
			.fip_subcode = FIP_SC_REQ, .fip_dl_len = cpu_to_be16(FIP_DISC_SOL_LEN),
			.fip_flags = cpu_to_be16(FIP_FL_FPMA)},
		.mac_desc = {.fd_desc = {.fip_dtype = FIP_DT_MAC, .fip_dlen = 2}},
		.name_desc = {.fd_desc = {.fip_dtype = FIP_DT_NAME, .fip_dlen = 3}},
		.fcoe_desc = {.fd_desc = {.fip_dtype = FIP_DT_FCOE_SIZE, .fip_dlen = 1},
			      .fd_size = cpu_to_be16(FCOE_MAX_SIZE)}
	};

	memcpy(pdisc_sol->eth.h_source, iport->hwmac, ETH_ALEN);
	memcpy(pdisc_sol->mac_desc.fd_mac, iport->hwmac, ETH_ALEN);
	iport->selected_fcf.fcf_priority = 0xFF;

	FNIC_STD_SET_NODE_NAME(&pdisc_sol->name_desc.fd_wwn, iport->wwnn);

	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "Start FCF discovery\n");
	fnic_send_fip_frame(iport, frame, frame_size);

	iport->fip.state = FDLS_FIP_FCF_DISCOVERY_STARTED;

	fcs_tov = jiffies + msecs_to_jiffies(FCOE_CTLR_FCS_TOV);
	mod_timer(&fnic->retry_fip_timer, round_jiffies(fcs_tov));
}

/**
 * fnic_fcoe_fip_discovery_resp - Processes FCF advertisements.
 * @fnic: Handle to fnic driver instance
 * @fiph: Received frame
 *
 * FCF advertisements can be:
 * solicited - Sent in response of a discover FCF FIP request
 * Store the information of the FCF with highest priority.
 * Wait until timeout in case of multiple FCFs.
 *
 * unsolicited - Sent periodically by the FCF for keep alive.
 * If FLOGI is in progress or completed and the advertisement is
 * received by our selected FCF, refresh the keep alive timer.
 */
void fnic_fcoe_fip_discovery_resp(struct fnic *fnic, struct fip_header *fiph)
{
	struct fnic_iport_s *iport = &fnic->iport;
	struct fip_disc_adv *disc_adv = (struct fip_disc_adv *)fiph;
	u64 fcs_ka_tov;
	u64 tov;
	int fka_has_changed;

	switch (iport->fip.state) {
	case FDLS_FIP_FCF_DISCOVERY_STARTED:
		if (be16_to_cpu(disc_adv->fip.fip_flags) & FIP_FL_SOL) {
			FNIC_FIP_DBG(KERN_INFO, fnic->host,
				     fnic->fnic_num,
				     "fnic 0x%p Solicited adv\n", fnic);

			if ((disc_adv->prio_desc.fd_pri <
			     iport->selected_fcf.fcf_priority)
			    && (be16_to_cpu(disc_adv->fip.fip_flags) & FIP_FL_AVAIL)) {

				FNIC_FIP_DBG(KERN_INFO, fnic->host,
					     fnic->fnic_num,
					     "fnic 0x%p FCF Available\n", fnic);
				memcpy(iport->selected_fcf.fcf_mac,
				       disc_adv->mac_desc.fd_mac, ETH_ALEN);
				iport->selected_fcf.fcf_priority =
				    disc_adv->prio_desc.fd_pri;
				iport->selected_fcf.fka_adv_period =
				    be32_to_cpu(disc_adv->fka_adv_desc.fd_fka_period);
				FNIC_FIP_DBG(KERN_INFO, fnic->host,
					     fnic->fnic_num, "adv time %d",
					     iport->selected_fcf.fka_adv_period);
				iport->selected_fcf.ka_disabled =
				    (disc_adv->fka_adv_desc.fd_flags & 1);
			}
		}
		break;
	case FDLS_FIP_FLOGI_STARTED:
	case FDLS_FIP_FLOGI_COMPLETE:
		if (!(be16_to_cpu(disc_adv->fip.fip_flags) & FIP_FL_SOL)) {
			/* same fcf */
			if (memcmp
			    (iport->selected_fcf.fcf_mac,
			     disc_adv->mac_desc.fd_mac, ETH_ALEN) == 0) {
				if (iport->selected_fcf.fka_adv_period !=
				    be32_to_cpu(disc_adv->fka_adv_desc.fd_fka_period)) {
					iport->selected_fcf.fka_adv_period =
					    be32_to_cpu(disc_adv->fka_adv_desc.fd_fka_period);
					FNIC_FIP_DBG(KERN_INFO,
						     fnic->host,
						     fnic->fnic_num,
						     "change fka to %d",
						     iport->selected_fcf.fka_adv_period);
				}

				fka_has_changed =
				    (iport->selected_fcf.ka_disabled == 1)
				    && ((disc_adv->fka_adv_desc.fd_flags & 1) ==
					0);

				iport->selected_fcf.ka_disabled =
				    (disc_adv->fka_adv_desc.fd_flags & 1);
				if (!((iport->selected_fcf.ka_disabled)
				      || (iport->selected_fcf.fka_adv_period ==
					  0))) {

					fcs_ka_tov = jiffies
					    + 3
					    *
					    msecs_to_jiffies(iport->selected_fcf.fka_adv_period);
					mod_timer(&fnic->fcs_ka_timer,
						  round_jiffies(fcs_ka_tov));
				} else {
					if (timer_pending(&fnic->fcs_ka_timer))
						del_timer_sync(&fnic->fcs_ka_timer);
				}

				if (fka_has_changed) {
					if (iport->selected_fcf.fka_adv_period != 0) {
						tov =
						 jiffies +
						 msecs_to_jiffies(
							 iport->selected_fcf.fka_adv_period);
						mod_timer(&fnic->enode_ka_timer,
							  round_jiffies(tov));

						tov =
						    jiffies +
						    msecs_to_jiffies
						    (FIP_VN_KA_PERIOD);
						mod_timer(&fnic->vn_ka_timer,
							  round_jiffies(tov));
					}
				}
			}
		}
		break;
	default:
		break;
	}			/* end switch */
}

/**
 * fnic_fcoe_start_flogi - Send FIP FLOGI to the selected FCF
 * @fnic: Handle to fnic driver instance
 */
void fnic_fcoe_start_flogi(struct fnic *fnic)
{
	uint8_t *frame;
	struct fnic_iport_s *iport = &fnic->iport;
	struct fip_flogi *pflogi_req;
	u64 flogi_tov;
	uint16_t oxid;
	uint16_t frame_size = sizeof(struct fip_flogi);

	frame = fdls_alloc_frame(iport);
	if (frame == NULL) {
		FNIC_FIP_DBG(KERN_ERR, fnic->host, fnic->fnic_num,
		     "Failed to allocate frame to start FIP FLOGI");
		return;
	}

	pflogi_req = (struct fip_flogi *) frame;
	*pflogi_req = (struct fip_flogi) {
		.eth = {
			.h_proto = cpu_to_be16(ETH_P_FIP)},
		.fip = {
			.fip_ver = FIP_VER_ENCAPS(FIP_VER),
			.fip_op = cpu_to_be16(FIP_OP_LS),
			.fip_subcode = FIP_SC_REQ,
			.fip_dl_len = cpu_to_be16(FIP_FLOGI_LEN),
			.fip_flags = cpu_to_be16(FIP_FL_FPMA)},
		.flogi_desc = {
				.fd_desc = {.fip_dtype = FIP_DT_FLOGI, .fip_dlen = 36},
			       .flogi = {
					 .fchdr = {
						   .fh_r_ctl = FC_RCTL_ELS_REQ,
						   .fh_d_id = {0xFF, 0xFF, 0xFE},
						   .fh_type = FC_TYPE_ELS,
						   .fh_f_ctl = {FNIC_ELS_REQ_FCTL, 0, 0},
						   .fh_rx_id = cpu_to_be16(FNIC_UNASSIGNED_RXID)},
					 .els = {
						 .fl_cmd = ELS_FLOGI,
						 .fl_csp = {
							    .sp_hi_ver =
							    FNIC_FC_PH_VER_HI,
							    .sp_lo_ver =
							    FNIC_FC_PH_VER_LO,
							    .sp_bb_cred =
							    cpu_to_be16
							    (FNIC_FC_B2B_CREDIT),
							    .sp_bb_data =
							    cpu_to_be16
							    (FNIC_FC_B2B_RDF_SZ)},
						 .fl_cssp[2].cp_class =
						 cpu_to_be16(FC_CPC_VALID | FC_CPC_SEQ)
						},
					}
			},
		.mac_desc = {.fd_desc = {.fip_dtype = FIP_DT_MAC, .fip_dlen = 2}}
	};

	memcpy(pflogi_req->eth.h_source, iport->hwmac, ETH_ALEN);
	if (iport->usefip)
		memcpy(pflogi_req->eth.h_dest, iport->selected_fcf.fcf_mac,
		       ETH_ALEN);

	oxid = fdls_alloc_oxid(iport, FNIC_FRAME_TYPE_FABRIC_FLOGI,
		&iport->active_oxid_fabric_req);
	if (oxid == FNIC_UNASSIGNED_OXID) {
		FNIC_FCS_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "Failed to allocate OXID to send FIP FLOGI");
		mempool_free(frame, fnic->frame_pool);
		return;
	}
	FNIC_STD_SET_OX_ID(pflogi_req->flogi_desc.flogi.fchdr, oxid);

	FNIC_STD_SET_NPORT_NAME(&pflogi_req->flogi_desc.flogi.els.fl_wwpn,
			iport->wwpn);
	FNIC_STD_SET_NODE_NAME(&pflogi_req->flogi_desc.flogi.els.fl_wwnn,
			iport->wwnn);

	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "FIP start FLOGI\n");
	fnic_send_fip_frame(iport, frame, frame_size);
	iport->fip.flogi_retry++;

	iport->fip.state = FDLS_FIP_FLOGI_STARTED;
	flogi_tov = jiffies + msecs_to_jiffies(fnic->config.flogi_timeout);
	mod_timer(&fnic->retry_fip_timer, round_jiffies(flogi_tov));
}

/**
 * fnic_fcoe_process_flogi_resp - Processes FLOGI response from FCF.
 * @fnic: Handle to fnic driver instance
 * @fiph: Received frame
 *
 * If successful save assigned fc_id and MAC, program firmware
 * and start fdls discovery, else restart vlan discovery.
 */
void fnic_fcoe_process_flogi_resp(struct fnic *fnic, struct fip_header *fiph)
{
	struct fnic_iport_s *iport = &fnic->iport;
	struct fip_flogi_rsp *flogi_rsp = (struct fip_flogi_rsp *)fiph;
	int desc_len;
	uint32_t s_id;
	int frame_type;
	uint16_t oxid;

	struct fnic_stats *fnic_stats = &fnic->fnic_stats;
	struct fc_frame_header *fchdr = &flogi_rsp->rsp_desc.flogi.fchdr;

	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "fnic 0x%p FIP FLOGI rsp\n", fnic);
	desc_len = be16_to_cpu(flogi_rsp->fip.fip_dl_len);
	if (desc_len != 38) {
		FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
			     "Invalid Descriptor List len (%x). Dropping frame\n",
			     desc_len);
		return;
	}

	if (!((flogi_rsp->rsp_desc.fd_desc.fip_dtype == 7)
	      && (flogi_rsp->rsp_desc.fd_desc.fip_dlen == 36))
	    || !((flogi_rsp->mac_desc.fd_desc.fip_dtype == 2)
		 && (flogi_rsp->mac_desc.fd_desc.fip_dlen == 2))) {
		FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
			     "Dropping frame invalid type and len mix\n");
		return;
	}

	frame_type = fnic_fdls_validate_and_get_frame_type(iport, fchdr);

	s_id = ntoh24(fchdr->fh_s_id);
	if ((fchdr->fh_f_ctl[0] != 0x98)
	    || (fchdr->fh_r_ctl != 0x23)
	    || (s_id != FC_FID_FLOGI)
	    || (frame_type != FNIC_FABRIC_FLOGI_RSP)
	    || (fchdr->fh_type != 0x01)) {
		FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
			     "Dropping invalid frame: s_id %x F %x R %x t %x OX_ID %x\n",
			     s_id, fchdr->fh_f_ctl[0], fchdr->fh_r_ctl,
			     fchdr->fh_type, FNIC_STD_GET_OX_ID(fchdr));
		return;
	}

	if (iport->fip.state == FDLS_FIP_FLOGI_STARTED) {
		FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
			     "fnic 0x%p rsp for pending FLOGI\n", fnic);

		oxid = FNIC_STD_GET_OX_ID(fchdr);
		fdls_free_oxid(iport, oxid, &iport->active_oxid_fabric_req);
		del_timer_sync(&fnic->retry_fip_timer);

		if ((be16_to_cpu(flogi_rsp->fip.fip_dl_len) == FIP_FLOGI_LEN)
		    && (flogi_rsp->rsp_desc.flogi.els.fl_cmd == ELS_LS_ACC)) {

			FNIC_FIP_DBG(KERN_INFO, fnic->host,
				     fnic->fnic_num,
				     "fnic 0x%p FLOGI success\n", fnic);
			memcpy(iport->fpma, flogi_rsp->mac_desc.fd_mac, ETH_ALEN);
			iport->fcid =
			    ntoh24(flogi_rsp->rsp_desc.flogi.fchdr.fh_d_id);

			iport->r_a_tov =
			    be32_to_cpu(flogi_rsp->rsp_desc.flogi.els.fl_csp.sp_r_a_tov);
			iport->e_d_tov =
			    be32_to_cpu(flogi_rsp->rsp_desc.flogi.els.fl_csp.sp_e_d_tov);
			memcpy(fnic->iport.fcfmac, iport->selected_fcf.fcf_mac,
			       ETH_ALEN);
			vnic_dev_add_addr(fnic->vdev, flogi_rsp->mac_desc.fd_mac);

			if (fnic_fdls_register_portid(iport, iport->fcid, NULL)
			    != 0) {
				FNIC_FIP_DBG(KERN_INFO, fnic->host,
					     fnic->fnic_num,
					     "fnic 0x%p flogi registration failed\n",
					     fnic);
				return;
			}

			iport->fip.state = FDLS_FIP_FLOGI_COMPLETE;
			iport->state = FNIC_IPORT_STATE_FABRIC_DISC;
			FNIC_FIP_DBG(KERN_INFO, fnic->host,
				     fnic->fnic_num, "iport->state:%d\n",
				     iport->state);
			fnic_fdls_disc_start(iport);
			if (!((iport->selected_fcf.ka_disabled)
			      || (iport->selected_fcf.fka_adv_period == 0))) {
				u64 tov;

				tov = jiffies
				    +
				    msecs_to_jiffies(iport->selected_fcf.fka_adv_period);
				mod_timer(&fnic->enode_ka_timer,
					  round_jiffies(tov));

				tov =
				    jiffies +
				    msecs_to_jiffies(FIP_VN_KA_PERIOD);
				mod_timer(&fnic->vn_ka_timer,
					  round_jiffies(tov));

			}
		} else {
			/*
			 * If there's FLOGI rejects - clear all
			 * fcf's & restart from scratch
			 */
			atomic64_inc(&fnic_stats->vlan_stats.flogi_rejects);
			/* start FCoE VLAN discovery */
			fnic_fcoe_send_vlan_req(fnic);

			iport->fip.state = FDLS_FIP_VLAN_DISCOVERY_STARTED;
		}
	}
}

/**
 * fnic_common_fip_cleanup - Clean up FCF info and timers in case of
 * link down/CVL
 * @fnic: Handle to fnic driver instance
 */
void fnic_common_fip_cleanup(struct fnic *fnic)
{

	struct fnic_iport_s *iport = &fnic->iport;

	if (!iport->usefip)
		return;
	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "fnic 0x%p fip cleanup\n", fnic);

	iport->fip.state = FDLS_FIP_INIT;

	del_timer_sync(&fnic->retry_fip_timer);
	del_timer_sync(&fnic->fcs_ka_timer);
	del_timer_sync(&fnic->enode_ka_timer);
	del_timer_sync(&fnic->vn_ka_timer);

	if (!is_zero_ether_addr(iport->fpma))
		vnic_dev_del_addr(fnic->vdev, iport->fpma);

	memset(iport->fpma, 0, ETH_ALEN);
	iport->fcid = 0;
	iport->r_a_tov = 0;
	iport->e_d_tov = 0;
	memset(fnic->iport.fcfmac, 0, ETH_ALEN);
	memset(iport->selected_fcf.fcf_mac, 0, ETH_ALEN);
	iport->selected_fcf.fcf_priority = 0;
	iport->selected_fcf.fka_adv_period = 0;
	iport->selected_fcf.ka_disabled = 0;

	fnic_fcoe_reset_vlans(fnic);
}

/**
 * fnic_fcoe_process_cvl - Processes Clear Virtual Link from FCF.
 * @fnic: Handle to fnic driver instance
 * @fiph: Received frame
 *
 * Verify that cvl is received from our current FCF for our assigned MAC
 * and clean up and restart the vlan discovery.
 */
void fnic_fcoe_process_cvl(struct fnic *fnic, struct fip_header *fiph)
{
	struct fnic_iport_s *iport = &fnic->iport;
	struct fip_cvl *cvl_msg = (struct fip_cvl *)fiph;
	int i;
	int found = false;
	int max_count = 0;

	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "fnic 0x%p clear virtual link handler\n", fnic);

	if (!((cvl_msg->fcf_mac_desc.fd_desc.fip_dtype == 2)
	      && (cvl_msg->fcf_mac_desc.fd_desc.fip_dlen == 2))
	    || !((cvl_msg->name_desc.fd_desc.fip_dtype == 4)
		 && (cvl_msg->name_desc.fd_desc.fip_dlen == 3))) {

		FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
			     "invalid mix: ft %x fl %x ndt %x ndl %x",
			     cvl_msg->fcf_mac_desc.fd_desc.fip_dtype,
			     cvl_msg->fcf_mac_desc.fd_desc.fip_dlen,
				 cvl_msg->name_desc.fd_desc.fip_dtype,
			     cvl_msg->name_desc.fd_desc.fip_dlen);
	}

	if (memcmp
	    (iport->selected_fcf.fcf_mac, cvl_msg->fcf_mac_desc.fd_mac, ETH_ALEN)
	    == 0) {
		for (i = 0; i < ((be16_to_cpu(fiph->fip_dl_len) / 5) - 1); i++) {
			if (!((cvl_msg->vn_ports_desc[i].fd_desc.fip_dtype == 11)
			      && (cvl_msg->vn_ports_desc[i].fd_desc.fip_dlen == 5))) {

				FNIC_FIP_DBG(KERN_INFO, fnic->host,
					     fnic->fnic_num,
					     "Invalid type and len mix type: %d len: %d\n",
					     cvl_msg->vn_ports_desc[i].fd_desc.fip_dtype,
					     cvl_msg->vn_ports_desc[i].fd_desc.fip_dlen);
			}
			if (memcmp
			    (iport->fpma, cvl_msg->vn_ports_desc[i].fd_mac,
			     ETH_ALEN) == 0) {
				found = true;
				break;
			}
		}
		if (!found)
			return;
		fnic_common_fip_cleanup(fnic);

		while (fnic->reset_in_progress == IN_PROGRESS) {
			spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags);
			wait_for_completion_timeout(&fnic->reset_completion_wait,
							msecs_to_jiffies(5000));
			spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);
			max_count++;
			if (max_count >= FIP_FNIC_RESET_WAIT_COUNT) {
				FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
					 "Rthr waited too long. Skipping handle link event %p\n",
					 fnic);
				return;
			}
			FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
				 "fnic reset in progress. Link event needs to wait %p",
				 fnic);
		}
		fnic->reset_in_progress = IN_PROGRESS;
		fnic_fdls_link_down(iport);
		fnic->reset_in_progress = NOT_IN_PROGRESS;
		complete(&fnic->reset_completion_wait);
		fnic_fcoe_send_vlan_req(fnic);
	}
}

/**
 * fdls_fip_recv_frame - Demultiplexer for FIP frames
 * @fnic: Handle to fnic driver instance
 * @frame: Received ethernet frame
 */
int fdls_fip_recv_frame(struct fnic *fnic, void *frame)
{
	struct ethhdr *eth = (struct ethhdr *)frame;
	struct fip_header *fiph;
	u16 op;
	u8 sub;
	int len = 2048;

	if (be16_to_cpu(eth->h_proto) == ETH_P_FIP) {
		fiph = (struct fip_header *)(eth + 1);
		op = be16_to_cpu(fiph->fip_op);
		sub = fiph->fip_subcode;

		fnic_debug_dump_fip_frame(fnic, eth, len, "Incoming");

		if (op == FIP_OP_DISC && sub == FIP_SC_REP)
			fnic_fcoe_fip_discovery_resp(fnic, fiph);
		else if (op == FIP_OP_VLAN && sub == FIP_SC_REP)
			fnic_fcoe_process_vlan_resp(fnic, fiph);
		else if (op == FIP_OP_CTRL && sub == FIP_SC_REP)
			fnic_fcoe_process_cvl(fnic, fiph);
		else if (op == FIP_OP_LS && sub == FIP_SC_REP)
			fnic_fcoe_process_flogi_resp(fnic, fiph);

		/* Return true if the frame was a FIP frame */
		return true;
	}

	FNIC_FCS_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		"Not a FIP Frame");
	return false;
}

void fnic_work_on_fip_timer(struct work_struct *work)
{
	struct fnic *fnic = container_of(work, struct fnic, fip_timer_work);
	struct fnic_iport_s *iport = &fnic->iport;

	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "FIP timeout\n");

	if (iport->fip.state == FDLS_FIP_VLAN_DISCOVERY_STARTED) {
		fnic_vlan_discovery_timeout(fnic);
	} else if (iport->fip.state == FDLS_FIP_FCF_DISCOVERY_STARTED) {
		u8 zmac[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 };

		FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
			     "FCF Discovery timeout\n");
		if (memcmp(iport->selected_fcf.fcf_mac, zmac, ETH_ALEN) != 0) {

			if (iport->flags & FNIC_FIRST_LINK_UP) {
				fnic_scsi_fcpio_reset(iport->fnic);
				iport->flags &= ~FNIC_FIRST_LINK_UP;
			}

			fnic_fcoe_start_flogi(fnic);
			if (!((iport->selected_fcf.ka_disabled)
			      || (iport->selected_fcf.fka_adv_period == 0))) {
				u64 fcf_tov;

				fcf_tov = jiffies
				    + 3
				    *
				    msecs_to_jiffies(iport->selected_fcf.fka_adv_period);
				mod_timer(&fnic->fcs_ka_timer,
					  round_jiffies(fcf_tov));
			}
		} else {
			FNIC_FIP_DBG(KERN_INFO, fnic->host,
				     fnic->fnic_num, "FCF Discovery timeout\n");
			fnic_vlan_discovery_timeout(fnic);
		}
	} else if (iport->fip.state == FDLS_FIP_FLOGI_STARTED) {
		fdls_schedule_oxid_free(iport, &iport->active_oxid_fabric_req);
		FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
			     "FLOGI timeout\n");
		if (iport->fip.flogi_retry < fnic->config.flogi_retries)
			fnic_fcoe_start_flogi(fnic);
		else
			fnic_vlan_discovery_timeout(fnic);
	}
}

/**
 * fnic_handle_fip_timer - Timeout handler for FIP discover phase.
 * @t: Handle to the timer list
 *
 * Based on the current state, start next phase or restart discovery.
 */
void fnic_handle_fip_timer(struct timer_list *t)
{
	struct fnic *fnic = from_timer(fnic, t, retry_fip_timer);

	INIT_WORK(&fnic->fip_timer_work, fnic_work_on_fip_timer);
	queue_work(fnic_fip_queue, &fnic->fip_timer_work);
}

/**
 * fnic_handle_enode_ka_timer - FIP node keep alive.
 * @t: Handle to the timer list
 */
void fnic_handle_enode_ka_timer(struct timer_list *t)
{
	uint8_t *frame;
	struct fnic *fnic = from_timer(fnic, t, enode_ka_timer);

	struct fnic_iport_s *iport = &fnic->iport;
	struct fip_enode_ka *penode_ka;
	u64 enode_ka_tov;
	uint16_t frame_size = sizeof(struct fip_enode_ka);

	if (iport->fip.state != FDLS_FIP_FLOGI_COMPLETE)
		return;

	if ((iport->selected_fcf.ka_disabled)
	    || (iport->selected_fcf.fka_adv_period == 0)) {
		return;
	}

	frame = fdls_alloc_frame(iport);
	if (frame == NULL) {
		FNIC_FIP_DBG(KERN_ERR, fnic->host, fnic->fnic_num,
		     "Failed to allocate frame to send enode ka");
		return;
	}

	penode_ka = (struct fip_enode_ka *) frame;
	*penode_ka = (struct fip_enode_ka) {
		.eth = {
			.h_proto = cpu_to_be16(ETH_P_FIP)},
		.fip = {
			.fip_ver = FIP_VER_ENCAPS(FIP_VER),
			.fip_op = cpu_to_be16(FIP_OP_CTRL),
			.fip_subcode = FIP_SC_REQ,
			.fip_dl_len = cpu_to_be16(FIP_ENODE_KA_LEN)},
		.mac_desc = {.fd_desc = {.fip_dtype = FIP_DT_MAC, .fip_dlen = 2}}
	};

	memcpy(penode_ka->eth.h_source, iport->hwmac, ETH_ALEN);
	memcpy(penode_ka->eth.h_dest, iport->selected_fcf.fcf_mac, ETH_ALEN);
	memcpy(penode_ka->mac_desc.fd_mac, iport->hwmac, ETH_ALEN);

	FNIC_FIP_DBG(KERN_DEBUG, fnic->host, fnic->fnic_num,
		     "Handle enode KA timer\n");
	fnic_send_fip_frame(iport, frame, frame_size);
	enode_ka_tov = jiffies
	    + msecs_to_jiffies(iport->selected_fcf.fka_adv_period);
	mod_timer(&fnic->enode_ka_timer, round_jiffies(enode_ka_tov));
}

/**
 * fnic_handle_vn_ka_timer - FIP virtual port keep alive.
 * @t: Handle to the timer list
 */
void fnic_handle_vn_ka_timer(struct timer_list *t)
{
	uint8_t *frame;
	struct fnic *fnic = from_timer(fnic, t, vn_ka_timer);

	struct fnic_iport_s *iport = &fnic->iport;
	struct fip_vn_port_ka *pvn_port_ka;
	u64 vn_ka_tov;
	uint8_t fcid[3];
	uint16_t frame_size = sizeof(struct fip_vn_port_ka);

	if (iport->fip.state != FDLS_FIP_FLOGI_COMPLETE)
		return;

	if ((iport->selected_fcf.ka_disabled)
	    || (iport->selected_fcf.fka_adv_period == 0)) {
		return;
	}

	frame = fdls_alloc_frame(iport);
	if (frame == NULL) {
		FNIC_FIP_DBG(KERN_ERR, fnic->host, fnic->fnic_num,
		     "Failed to allocate frame to send vn ka");
		return;
	}

	pvn_port_ka = (struct fip_vn_port_ka *) frame;
	*pvn_port_ka = (struct fip_vn_port_ka) {
		.eth = {
			.h_proto = cpu_to_be16(ETH_P_FIP)},
		.fip = {
			.fip_ver = FIP_VER_ENCAPS(FIP_VER),
			.fip_op = cpu_to_be16(FIP_OP_CTRL),
			.fip_subcode = FIP_SC_REQ,
			.fip_dl_len = cpu_to_be16(FIP_VN_KA_LEN)},
		.mac_desc = {.fd_desc = {.fip_dtype = FIP_DT_MAC, .fip_dlen = 2}},
		.vn_port_desc = {.fd_desc = {.fip_dtype = FIP_DT_VN_ID, .fip_dlen = 5}}
	};

	memcpy(pvn_port_ka->eth.h_source, iport->fpma, ETH_ALEN);
	memcpy(pvn_port_ka->eth.h_dest, iport->selected_fcf.fcf_mac, ETH_ALEN);
	memcpy(pvn_port_ka->mac_desc.fd_mac, iport->hwmac, ETH_ALEN);
	memcpy(pvn_port_ka->vn_port_desc.fd_mac, iport->fpma, ETH_ALEN);
	hton24(fcid, iport->fcid);
	memcpy(pvn_port_ka->vn_port_desc.fd_fc_id, fcid, 3);
	FNIC_STD_SET_NPORT_NAME(&pvn_port_ka->vn_port_desc.fd_wwpn, iport->wwpn);

	FNIC_FIP_DBG(KERN_DEBUG, fnic->host, fnic->fnic_num,
		     "Handle vnport KA timer\n");
	fnic_send_fip_frame(iport, frame, frame_size);
	vn_ka_tov = jiffies + msecs_to_jiffies(FIP_VN_KA_PERIOD);
	mod_timer(&fnic->vn_ka_timer, round_jiffies(vn_ka_tov));
}

/**
 * fnic_vlan_discovery_timeout - Handle vlan discovery timeout
 * @fnic: Handle to fnic driver instance
 *
 * End of VLAN discovery or FCF discovery time window.
 * Start the FCF discovery if VLAN was never used.
 */
void fnic_vlan_discovery_timeout(struct fnic *fnic)
{
	struct fcoe_vlan *vlan;
	struct fnic_iport_s *iport = &fnic->iport;
	struct fnic_stats *fnic_stats = &fnic->fnic_stats;
	unsigned long flags;

	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 (!iport->usefip)
		return;

	spin_lock_irqsave(&fnic->vlans_lock, flags);
	if (list_empty(&fnic->vlan_list)) {
		/* no vlans available, try again */
		spin_unlock_irqrestore(&fnic->vlans_lock, flags);
		fnic_fcoe_send_vlan_req(fnic);
		return;
	}

	vlan = list_first_entry(&fnic->vlan_list, struct fcoe_vlan, list);

	if (vlan->state == FIP_VLAN_SENT) {
		if (vlan->sol_count >= FCOE_CTLR_MAX_SOL) {
			/*
			 * no response on this vlan, remove  from the list.
			 * Try the next vlan
			 */
			list_del(&vlan->list);
			kfree(vlan);
			vlan = NULL;
			if (list_empty(&fnic->vlan_list)) {
				/* we exhausted all vlans, restart vlan disc */
				spin_unlock_irqrestore(&fnic->vlans_lock,
						       flags);
				fnic_fcoe_send_vlan_req(fnic);
				return;
			}
			/* check the next vlan */
			vlan =
			    list_first_entry(&fnic->vlan_list, struct fcoe_vlan,
					     list);

			fnic->set_vlan(fnic, vlan->vid);
			vlan->state = FIP_VLAN_SENT;	/* sent now */

		}
		atomic64_inc(&fnic_stats->vlan_stats.sol_expiry_count);

	} else {
		fnic->set_vlan(fnic, vlan->vid);
		vlan->state = FIP_VLAN_SENT;	/* sent now */
	}
	vlan->sol_count++;
	spin_unlock_irqrestore(&fnic->vlans_lock, flags);
	fnic_fcoe_start_fcf_discovery(fnic);
}

/**
 * fnic_work_on_fcs_ka_timer - Handle work on FCS keep alive timer.
 * @work: the work queue to be serviced
 *
 * Finish handling fcs_ka_timer in process context.
 * Clean up, bring the link down, and restart all FIP discovery.
 */
void fnic_work_on_fcs_ka_timer(struct work_struct *work)
{
	struct fnic
	*fnic = container_of(work, struct fnic, fip_timer_work);
	struct fnic_iport_s *iport = &fnic->iport;

	FNIC_FIP_DBG(KERN_INFO, fnic->host, fnic->fnic_num,
		     "fnic 0x%p fcs ka timeout\n", fnic);

	fnic_common_fip_cleanup(fnic);
	spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);
	fnic_fdls_link_down(iport);
	iport->state = FNIC_IPORT_STATE_FIP;
	spin_unlock_irqrestore(&fnic->fnic_lock, fnic->lock_flags);

	fnic_fcoe_send_vlan_req(fnic);
}

/**
 * fnic_handle_fcs_ka_timer - Handle FCS keep alive timer.
 * @t: Handle to the timer list
 *
 * No keep alives received from FCF. Clean up, bring the link down
 * and restart all the FIP discovery.
 */
void fnic_handle_fcs_ka_timer(struct timer_list *t)
{
	struct fnic *fnic = from_timer(fnic, t, fcs_ka_timer);

	INIT_WORK(&fnic->fip_timer_work, fnic_work_on_fcs_ka_timer);
	queue_work(fnic_fip_queue, &fnic->fip_timer_work);
}