root/drivers/scsi/fnic/fip.c
// 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_obj(*vlan);

                        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;
        }

        eth_zero_addr(iport->selected_fcf.fcf_mac);

        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))
                                                timer_delete_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);
                timer_delete_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;

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

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

        eth_zero_addr(iport->fpma);
        iport->fcid = 0;
        iport->r_a_tov = 0;
        iport->e_d_tov = 0;
        eth_zero_addr(fnic->iport.fcfmac);
        eth_zero_addr(iport->selected_fcf.fcf_mac);
        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_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 = timer_container_of(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 = timer_container_of(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 = timer_container_of(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 = timer_container_of(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);
}