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