/* * Copyright (c) 2005-2009 Brocade Communications Systems, Inc. * All rights reserved * www.brocade.com * * Linux driver for Brocade Fibre Channel Host Bus Adapter. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License (GPL) Version 2 as * published by the Free Software Foundation * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ #include #include #include #include BFA_TRC_FILE(HAL, LPS); BFA_MODULE(lps); #define BFA_LPS_MIN_LPORTS (1) #define BFA_LPS_MAX_LPORTS (256) /* * Maximum Vports supported per physical port or vf. */ #define BFA_LPS_MAX_VPORTS_SUPP_CB 255 #define BFA_LPS_MAX_VPORTS_SUPP_CT 190 /** * forward declarations */ static void bfa_lps_meminfo(struct bfa_iocfc_cfg_s *cfg, u32 *ndm_len, u32 *dm_len); static void bfa_lps_attach(struct bfa_s *bfa, void *bfad, struct bfa_iocfc_cfg_s *cfg, struct bfa_meminfo_s *meminfo, struct bfa_pcidev_s *pcidev); static void bfa_lps_detach(struct bfa_s *bfa); static void bfa_lps_start(struct bfa_s *bfa); static void bfa_lps_stop(struct bfa_s *bfa); static void bfa_lps_iocdisable(struct bfa_s *bfa); static void bfa_lps_login_rsp(struct bfa_s *bfa, struct bfi_lps_login_rsp_s *rsp); static void bfa_lps_logout_rsp(struct bfa_s *bfa, struct bfi_lps_logout_rsp_s *rsp); static void bfa_lps_reqq_resume(void *lps_arg); static void bfa_lps_free(struct bfa_lps_s *lps); static void bfa_lps_send_login(struct bfa_lps_s *lps); static void bfa_lps_send_logout(struct bfa_lps_s *lps); static void bfa_lps_login_comp(struct bfa_lps_s *lps); static void bfa_lps_logout_comp(struct bfa_lps_s *lps); static void bfa_lps_cvl_event(struct bfa_lps_s *lps); /** * lps_pvt BFA LPS private functions */ enum bfa_lps_event { BFA_LPS_SM_LOGIN = 1, /* login request from user */ BFA_LPS_SM_LOGOUT = 2, /* logout request from user */ BFA_LPS_SM_FWRSP = 3, /* f/w response to login/logout */ BFA_LPS_SM_RESUME = 4, /* space present in reqq queue */ BFA_LPS_SM_DELETE = 5, /* lps delete from user */ BFA_LPS_SM_OFFLINE = 6, /* Link is offline */ BFA_LPS_SM_RX_CVL = 7, /* Rx clear virtual link */ }; static void bfa_lps_sm_init(struct bfa_lps_s *lps, enum bfa_lps_event event); static void bfa_lps_sm_login(struct bfa_lps_s *lps, enum bfa_lps_event event); static void bfa_lps_sm_loginwait(struct bfa_lps_s *lps, enum bfa_lps_event event); static void bfa_lps_sm_online(struct bfa_lps_s *lps, enum bfa_lps_event event); static void bfa_lps_sm_logout(struct bfa_lps_s *lps, enum bfa_lps_event event); static void bfa_lps_sm_logowait(struct bfa_lps_s *lps, enum bfa_lps_event event); /** * Init state -- no login */ static void bfa_lps_sm_init(struct bfa_lps_s *lps, enum bfa_lps_event event) { bfa_trc(lps->bfa, lps->lp_tag); bfa_trc(lps->bfa, event); switch (event) { case BFA_LPS_SM_LOGIN: if (bfa_reqq_full(lps->bfa, lps->reqq)) { bfa_sm_set_state(lps, bfa_lps_sm_loginwait); bfa_reqq_wait(lps->bfa, lps->reqq, &lps->wqe); } else { bfa_sm_set_state(lps, bfa_lps_sm_login); bfa_lps_send_login(lps); } if (lps->fdisc) bfa_plog_str(lps->bfa->plog, BFA_PL_MID_LPS, BFA_PL_EID_LOGIN, 0, "FDISC Request"); else bfa_plog_str(lps->bfa->plog, BFA_PL_MID_LPS, BFA_PL_EID_LOGIN, 0, "FLOGI Request"); break; case BFA_LPS_SM_LOGOUT: bfa_lps_logout_comp(lps); break; case BFA_LPS_SM_DELETE: bfa_lps_free(lps); break; case BFA_LPS_SM_RX_CVL: case BFA_LPS_SM_OFFLINE: break; case BFA_LPS_SM_FWRSP: /* Could happen when fabric detects loopback and discards * the lps request. Fw will eventually sent out the timeout * Just ignore */ break; default: bfa_sm_fault(lps->bfa, event); } } /** * login is in progress -- awaiting response from firmware */ static void bfa_lps_sm_login(struct bfa_lps_s *lps, enum bfa_lps_event event) { bfa_trc(lps->bfa, lps->lp_tag); bfa_trc(lps->bfa, event); switch (event) { case BFA_LPS_SM_FWRSP: if (lps->status == BFA_STATUS_OK) { bfa_sm_set_state(lps, bfa_lps_sm_online); if (lps->fdisc) bfa_plog_str(lps->bfa->plog, BFA_PL_MID_LPS, BFA_PL_EID_LOGIN, 0, "FDISC Accept"); else bfa_plog_str(lps->bfa->plog, BFA_PL_MID_LPS, BFA_PL_EID_LOGIN, 0, "FLOGI Accept"); } else { bfa_sm_set_state(lps, bfa_lps_sm_init); if (lps->fdisc) bfa_plog_str(lps->bfa->plog, BFA_PL_MID_LPS, BFA_PL_EID_LOGIN, 0, "FDISC Fail (RJT or timeout)"); else bfa_plog_str(lps->bfa->plog, BFA_PL_MID_LPS, BFA_PL_EID_LOGIN, 0, "FLOGI Fail (RJT or timeout)"); } bfa_lps_login_comp(lps); break; case BFA_LPS_SM_OFFLINE: bfa_sm_set_state(lps, bfa_lps_sm_init); break; default: bfa_sm_fault(lps->bfa, event); } } /** * login pending - awaiting space in request queue */ static void bfa_lps_sm_loginwait(struct bfa_lps_s *lps, enum bfa_lps_event event) { bfa_trc(lps->bfa, lps->lp_tag); bfa_trc(lps->bfa, event); switch (event) { case BFA_LPS_SM_RESUME: bfa_sm_set_state(lps, bfa_lps_sm_login); break; case BFA_LPS_SM_OFFLINE: bfa_sm_set_state(lps, bfa_lps_sm_init); bfa_reqq_wcancel(&lps->wqe); break; case BFA_LPS_SM_RX_CVL: /* * Login was not even sent out; so when getting out * of this state, it will appear like a login retry * after Clear virtual link */ break; default: bfa_sm_fault(lps->bfa, event); } } /** * login complete */ static void bfa_lps_sm_online(struct bfa_lps_s *lps, enum bfa_lps_event event) { bfa_trc(lps->bfa, lps->lp_tag); bfa_trc(lps->bfa, event); switch (event) { case BFA_LPS_SM_LOGOUT: if (bfa_reqq_full(lps->bfa, lps->reqq)) { bfa_sm_set_state(lps, bfa_lps_sm_logowait); bfa_reqq_wait(lps->bfa, lps->reqq, &lps->wqe); } else { bfa_sm_set_state(lps, bfa_lps_sm_logout); bfa_lps_send_logout(lps); } bfa_plog_str(lps->bfa->plog, BFA_PL_MID_LPS, BFA_PL_EID_LOGO, 0, "Logout"); break; case BFA_LPS_SM_RX_CVL: bfa_sm_set_state(lps, bfa_lps_sm_init); /* Let the vport module know about this event */ bfa_lps_cvl_event(lps); bfa_plog_str(lps->bfa->plog, BFA_PL_MID_LPS, BFA_PL_EID_FIP_FCF_CVL, 0, "FCF Clear Virt. Link Rx"); break; case BFA_LPS_SM_OFFLINE: case BFA_LPS_SM_DELETE: bfa_sm_set_state(lps, bfa_lps_sm_init); break; default: bfa_sm_fault(lps->bfa, event); } } /** * logout in progress - awaiting firmware response */ static void bfa_lps_sm_logout(struct bfa_lps_s *lps, enum bfa_lps_event event) { bfa_trc(lps->bfa, lps->lp_tag); bfa_trc(lps->bfa, event); switch (event) { case BFA_LPS_SM_FWRSP: bfa_sm_set_state(lps, bfa_lps_sm_init); bfa_lps_logout_comp(lps); break; case BFA_LPS_SM_OFFLINE: bfa_sm_set_state(lps, bfa_lps_sm_init); break; default: bfa_sm_fault(lps->bfa, event); } } /** * logout pending -- awaiting space in request queue */ static void bfa_lps_sm_logowait(struct bfa_lps_s *lps, enum bfa_lps_event event) { bfa_trc(lps->bfa, lps->lp_tag); bfa_trc(lps->bfa, event); switch (event) { case BFA_LPS_SM_RESUME: bfa_sm_set_state(lps, bfa_lps_sm_logout); bfa_lps_send_logout(lps); break; case BFA_LPS_SM_OFFLINE: bfa_sm_set_state(lps, bfa_lps_sm_init); bfa_reqq_wcancel(&lps->wqe); break; default: bfa_sm_fault(lps->bfa, event); } } /** * lps_pvt BFA LPS private functions */ /** * return memory requirement */ static void bfa_lps_meminfo(struct bfa_iocfc_cfg_s *cfg, u32 *ndm_len, u32 *dm_len) { if (cfg->drvcfg.min_cfg) *ndm_len += sizeof(struct bfa_lps_s) * BFA_LPS_MIN_LPORTS; else *ndm_len += sizeof(struct bfa_lps_s) * BFA_LPS_MAX_LPORTS; } /** * bfa module attach at initialization time */ static void bfa_lps_attach(struct bfa_s *bfa, void *bfad, struct bfa_iocfc_cfg_s *cfg, struct bfa_meminfo_s *meminfo, struct bfa_pcidev_s *pcidev) { struct bfa_lps_mod_s *mod = BFA_LPS_MOD(bfa); struct bfa_lps_s *lps; int i; bfa_os_memset(mod, 0, sizeof(struct bfa_lps_mod_s)); mod->num_lps = BFA_LPS_MAX_LPORTS; if (cfg->drvcfg.min_cfg) mod->num_lps = BFA_LPS_MIN_LPORTS; else mod->num_lps = BFA_LPS_MAX_LPORTS; mod->lps_arr = lps = (struct bfa_lps_s *) bfa_meminfo_kva(meminfo); bfa_meminfo_kva(meminfo) += mod->num_lps * sizeof(struct bfa_lps_s); INIT_LIST_HEAD(&mod->lps_free_q); INIT_LIST_HEAD(&mod->lps_active_q); for (i = 0; i < mod->num_lps; i++, lps++) { lps->bfa = bfa; lps->lp_tag = (u8) i; lps->reqq = BFA_REQQ_LPS; bfa_reqq_winit(&lps->wqe, bfa_lps_reqq_resume, lps); list_add_tail(&lps->qe, &mod->lps_free_q); } } static void bfa_lps_detach(struct bfa_s *bfa) { } static void bfa_lps_start(struct bfa_s *bfa) { } static void bfa_lps_stop(struct bfa_s *bfa) { } /** * IOC in disabled state -- consider all lps offline */ static void bfa_lps_iocdisable(struct bfa_s *bfa) { struct bfa_lps_mod_s *mod = BFA_LPS_MOD(bfa); struct bfa_lps_s *lps; struct list_head *qe, *qen; list_for_each_safe(qe, qen, &mod->lps_active_q) { lps = (struct bfa_lps_s *) qe; bfa_sm_send_event(lps, BFA_LPS_SM_OFFLINE); } } /** * Firmware login response */ static void bfa_lps_login_rsp(struct bfa_s *bfa, struct bfi_lps_login_rsp_s *rsp) { struct bfa_lps_mod_s *mod = BFA_LPS_MOD(bfa); struct bfa_lps_s *lps; bfa_assert(rsp->lp_tag < mod->num_lps); lps = BFA_LPS_FROM_TAG(mod, rsp->lp_tag); lps->status = rsp->status; switch (rsp->status) { case BFA_STATUS_OK: lps->fport = rsp->f_port; lps->npiv_en = rsp->npiv_en; lps->lp_pid = rsp->lp_pid; lps->pr_bbcred = bfa_os_ntohs(rsp->bb_credit); lps->pr_pwwn = rsp->port_name; lps->pr_nwwn = rsp->node_name; lps->auth_req = rsp->auth_req; lps->lp_mac = rsp->lp_mac; lps->brcd_switch = rsp->brcd_switch; lps->fcf_mac = rsp->fcf_mac; break; case BFA_STATUS_FABRIC_RJT: lps->lsrjt_rsn = rsp->lsrjt_rsn; lps->lsrjt_expl = rsp->lsrjt_expl; break; case BFA_STATUS_EPROTOCOL: lps->ext_status = rsp->ext_status; break; default: /* Nothing to do with other status */ break; } bfa_sm_send_event(lps, BFA_LPS_SM_FWRSP); } /** * Firmware logout response */ static void bfa_lps_logout_rsp(struct bfa_s *bfa, struct bfi_lps_logout_rsp_s *rsp) { struct bfa_lps_mod_s *mod = BFA_LPS_MOD(bfa); struct bfa_lps_s *lps; bfa_assert(rsp->lp_tag < mod->num_lps); lps = BFA_LPS_FROM_TAG(mod, rsp->lp_tag); bfa_sm_send_event(lps, BFA_LPS_SM_FWRSP); } /** * Firmware received a Clear virtual link request (for FCoE) */ static void bfa_lps_rx_cvl_event(struct bfa_s *bfa, struct bfi_lps_cvl_event_s *cvl) { struct bfa_lps_mod_s *mod = BFA_LPS_MOD(bfa); struct bfa_lps_s *lps; lps = BFA_LPS_FROM_TAG(mod, cvl->lp_tag); bfa_sm_send_event(lps, BFA_LPS_SM_RX_CVL); } /** * Space is available in request queue, resume queueing request to firmware. */ static void bfa_lps_reqq_resume(void *lps_arg) { struct bfa_lps_s *lps = lps_arg; bfa_sm_send_event(lps, BFA_LPS_SM_RESUME); } /** * lps is freed -- triggered by vport delete */ static void bfa_lps_free(struct bfa_lps_s *lps) { struct bfa_lps_mod_s *mod = BFA_LPS_MOD(lps->bfa); list_del(&lps->qe); list_add_tail(&lps->qe, &mod->lps_free_q); } /** * send login request to firmware */ static void bfa_lps_send_login(struct bfa_lps_s *lps) { struct bfi_lps_login_req_s *m; m = bfa_reqq_next(lps->bfa, lps->reqq); bfa_assert(m); bfi_h2i_set(m->mh, BFI_MC_LPS, BFI_LPS_H2I_LOGIN_REQ, bfa_lpuid(lps->bfa)); m->lp_tag = lps->lp_tag; m->alpa = lps->alpa; m->pdu_size = bfa_os_htons(lps->pdusz); m->pwwn = lps->pwwn; m->nwwn = lps->nwwn; m->fdisc = lps->fdisc; m->auth_en = lps->auth_en; bfa_reqq_produce(lps->bfa, lps->reqq); } /** * send logout request to firmware */ static void bfa_lps_send_logout(struct bfa_lps_s *lps) { struct bfi_lps_logout_req_s *m; m = bfa_reqq_next(lps->bfa, lps->reqq); bfa_assert(m); bfi_h2i_set(m->mh, BFI_MC_LPS, BFI_LPS_H2I_LOGOUT_REQ, bfa_lpuid(lps->bfa)); m->lp_tag = lps->lp_tag; m->port_name = lps->pwwn; bfa_reqq_produce(lps->bfa, lps->reqq); } /** * Indirect login completion handler for non-fcs */ static void bfa_lps_login_comp_cb(void *arg, bfa_boolean_t complete) { struct bfa_lps_s *lps = arg; if (!complete) return; if (lps->fdisc) bfa_cb_lps_fdisc_comp(lps->bfa->bfad, lps->uarg, lps->status); else bfa_cb_lps_flogi_comp(lps->bfa->bfad, lps->uarg, lps->status); } /** * Login completion handler -- direct call for fcs, queue for others */ static void bfa_lps_login_comp(struct bfa_lps_s *lps) { if (!lps->bfa->fcs) { bfa_cb_queue(lps->bfa, &lps->hcb_qe, bfa_lps_login_comp_cb, lps); return; } if (lps->fdisc) bfa_cb_lps_fdisc_comp(lps->bfa->bfad, lps->uarg, lps->status); else bfa_cb_lps_flogi_comp(lps->bfa->bfad, lps->uarg, lps->status); } /** * Indirect logout completion handler for non-fcs */ static void bfa_lps_logout_comp_cb(void *arg, bfa_boolean_t complete) { struct bfa_lps_s *lps = arg; if (!complete) return; if (lps->fdisc) bfa_cb_lps_fdisclogo_comp(lps->bfa->bfad, lps->uarg); else bfa_cb_lps_flogo_comp(lps->bfa->bfad, lps->uarg); } /** * Logout completion handler -- direct call for fcs, queue for others */ static void bfa_lps_logout_comp(struct bfa_lps_s *lps) { if (!lps->bfa->fcs) { bfa_cb_queue(lps->bfa, &lps->hcb_qe, bfa_lps_logout_comp_cb, lps); return; } if (lps->fdisc) bfa_cb_lps_fdisclogo_comp(lps->bfa->bfad, lps->uarg); else bfa_cb_lps_flogo_comp(lps->bfa->bfad, lps->uarg); } /** * Clear virtual link completion handler for non-fcs */ static void bfa_lps_cvl_event_cb(void *arg, bfa_boolean_t complete) { struct bfa_lps_s *lps = arg; if (!complete) return; /* Clear virtual link to base port will result in link down */ if (lps->fdisc) bfa_cb_lps_cvl_event(lps->bfa->bfad, lps->uarg); } /** * Received Clear virtual link event --direct call for fcs, * queue for others */ static void bfa_lps_cvl_event(struct bfa_lps_s *lps) { if (!lps->bfa->fcs) { bfa_cb_queue(lps->bfa, &lps->hcb_qe, bfa_lps_cvl_event_cb, lps); return; } /* Clear virtual link to base port will result in link down */ if (lps->fdisc) bfa_cb_lps_cvl_event(lps->bfa->bfad, lps->uarg); } u32 bfa_lps_get_max_vport(struct bfa_s *bfa) { if (bfa_ioc_devid(&bfa->ioc) == BFA_PCI_DEVICE_ID_CT) return BFA_LPS_MAX_VPORTS_SUPP_CT; else return BFA_LPS_MAX_VPORTS_SUPP_CB; } /** * lps_public BFA LPS public functions */ /** * Allocate a lport srvice tag. */ struct bfa_lps_s * bfa_lps_alloc(struct bfa_s *bfa) { struct bfa_lps_mod_s *mod = BFA_LPS_MOD(bfa); struct bfa_lps_s *lps = NULL; bfa_q_deq(&mod->lps_free_q, &lps); if (lps == NULL) return NULL; list_add_tail(&lps->qe, &mod->lps_active_q); bfa_sm_set_state(lps, bfa_lps_sm_init); return lps; } /** * Free lport service tag. This can be called anytime after an alloc. * No need to wait for any pending login/logout completions. */ void bfa_lps_delete(struct bfa_lps_s *lps) { bfa_sm_send_event(lps, BFA_LPS_SM_DELETE); } /** * Initiate a lport login. */ void bfa_lps_flogi(struct bfa_lps_s *lps, void *uarg, u8 alpa, u16 pdusz, wwn_t pwwn, wwn_t nwwn, bfa_boolean_t auth_en) { lps->uarg = uarg; lps->alpa = alpa; lps->pdusz = pdusz; lps->pwwn = pwwn; lps->nwwn = nwwn; lps->fdisc = BFA_FALSE; lps->auth_en = auth_en; bfa_sm_send_event(lps, BFA_LPS_SM_LOGIN); } /** * Initiate a lport fdisc login. */ void bfa_lps_fdisc(struct bfa_lps_s *lps, void *uarg, u16 pdusz, wwn_t pwwn, wwn_t nwwn) { lps->uarg = uarg; lps->alpa = 0; lps->pdusz = pdusz; lps->pwwn = pwwn; lps->nwwn = nwwn; lps->fdisc = BFA_TRUE; lps->auth_en = BFA_FALSE; bfa_sm_send_event(lps, BFA_LPS_SM_LOGIN); } /** * Initiate a lport logout (flogi). */ void bfa_lps_flogo(struct bfa_lps_s *lps) { bfa_sm_send_event(lps, BFA_LPS_SM_LOGOUT); } /** * Initiate a lport FDSIC logout. */ void bfa_lps_fdisclogo(struct bfa_lps_s *lps) { bfa_sm_send_event(lps, BFA_LPS_SM_LOGOUT); } /** * Discard a pending login request -- should be called only for * link down handling. */ void bfa_lps_discard(struct bfa_lps_s *lps) { bfa_sm_send_event(lps, BFA_LPS_SM_OFFLINE); } /** * Return lport services tag */ u8 bfa_lps_get_tag(struct bfa_lps_s *lps) { return lps->lp_tag; } /** * Return lport services tag given the pid */ u8 bfa_lps_get_tag_from_pid(struct bfa_s *bfa, u32 pid) { struct bfa_lps_mod_s *mod = BFA_LPS_MOD(bfa); struct bfa_lps_s *lps; int i; for (i = 0, lps = mod->lps_arr; i < mod->num_lps; i++, lps++) { if (lps->lp_pid == pid) return lps->lp_tag; } /* Return base port tag anyway */ return 0; } /** * return if fabric login indicates support for NPIV */ bfa_boolean_t bfa_lps_is_npiv_en(struct bfa_lps_s *lps) { return lps->npiv_en; } /** * Return TRUE if attached to F-Port, else return FALSE */ bfa_boolean_t bfa_lps_is_fport(struct bfa_lps_s *lps) { return lps->fport; } /** * Return TRUE if attached to a Brocade Fabric */ bfa_boolean_t bfa_lps_is_brcd_fabric(struct bfa_lps_s *lps) { return lps->brcd_switch; } /** * return TRUE if authentication is required */ bfa_boolean_t bfa_lps_is_authreq(struct bfa_lps_s *lps) { return lps->auth_req; } bfa_eproto_status_t bfa_lps_get_extstatus(struct bfa_lps_s *lps) { return lps->ext_status; } /** * return port id assigned to the lport */ u32 bfa_lps_get_pid(struct bfa_lps_s *lps) { return lps->lp_pid; } /** * Return bb_credit assigned in FLOGI response */ u16 bfa_lps_get_peer_bbcredit(struct bfa_lps_s *lps) { return lps->pr_bbcred; } /** * Return peer port name */ wwn_t bfa_lps_get_peer_pwwn(struct bfa_lps_s *lps) { return lps->pr_pwwn; } /** * Return peer node name */ wwn_t bfa_lps_get_peer_nwwn(struct bfa_lps_s *lps) { return lps->pr_nwwn; } /** * return reason code if login request is rejected */ u8 bfa_lps_get_lsrjt_rsn(struct bfa_lps_s *lps) { return lps->lsrjt_rsn; } /** * return explanation code if login request is rejected */ u8 bfa_lps_get_lsrjt_expl(struct bfa_lps_s *lps) { return lps->lsrjt_expl; } /** * Return fpma/spma MAC for lport */ struct mac_s bfa_lps_get_lp_mac(struct bfa_lps_s *lps) { return lps->lp_mac; } /** * LPS firmware message class handler. */ void bfa_lps_isr(struct bfa_s *bfa, struct bfi_msg_s *m) { union bfi_lps_i2h_msg_u msg; bfa_trc(bfa, m->mhdr.msg_id); msg.msg = m; switch (m->mhdr.msg_id) { case BFI_LPS_H2I_LOGIN_RSP: bfa_lps_login_rsp(bfa, msg.login_rsp); break; case BFI_LPS_H2I_LOGOUT_RSP: bfa_lps_logout_rsp(bfa, msg.logout_rsp); break; case BFI_LPS_H2I_CVL_EVENT: bfa_lps_rx_cvl_event(bfa, msg.cvl_event); break; default: bfa_trc(bfa, m->mhdr.msg_id); bfa_assert(0); } }