/* * 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 "fcs_lport.h" #include "fcs_rport.h" #include "fcs_ms.h" #include "fcs_trcmod.h" #include "fcs_fcxp.h" #include "fcs.h" #include "lport_priv.h" BFA_TRC_FILE(FCS, SCN); #define FC_QOS_RSCN_EVENT 0x0c #define FC_FABRIC_NAME_RSCN_EVENT 0x0d /* * forward declarations */ static void bfa_fcs_port_scn_send_scr(void *scn_cbarg, struct bfa_fcxp_s *fcxp_alloced); static void bfa_fcs_port_scn_scr_response(void *fcsarg, struct bfa_fcxp_s *fcxp, void *cbarg, bfa_status_t req_status, u32 rsp_len, u32 resid_len, struct fchs_s *rsp_fchs); static void bfa_fcs_port_scn_send_ls_acc(struct bfa_fcs_port_s *port, struct fchs_s *rx_fchs); static void bfa_fcs_port_scn_timeout(void *arg); /** * fcs_scm_sm FCS SCN state machine */ /** * VPort SCN State Machine events */ enum port_scn_event { SCNSM_EVENT_PORT_ONLINE = 1, SCNSM_EVENT_PORT_OFFLINE = 2, SCNSM_EVENT_RSP_OK = 3, SCNSM_EVENT_RSP_ERROR = 4, SCNSM_EVENT_TIMEOUT = 5, SCNSM_EVENT_SCR_SENT = 6, }; static void bfa_fcs_port_scn_sm_offline(struct bfa_fcs_port_scn_s *scn, enum port_scn_event event); static void bfa_fcs_port_scn_sm_sending_scr(struct bfa_fcs_port_scn_s *scn, enum port_scn_event event); static void bfa_fcs_port_scn_sm_scr(struct bfa_fcs_port_scn_s *scn, enum port_scn_event event); static void bfa_fcs_port_scn_sm_scr_retry(struct bfa_fcs_port_scn_s *scn, enum port_scn_event event); static void bfa_fcs_port_scn_sm_online(struct bfa_fcs_port_scn_s *scn, enum port_scn_event event); /** * Starting state - awaiting link up. */ static void bfa_fcs_port_scn_sm_offline(struct bfa_fcs_port_scn_s *scn, enum port_scn_event event) { switch (event) { case SCNSM_EVENT_PORT_ONLINE: bfa_sm_set_state(scn, bfa_fcs_port_scn_sm_sending_scr); bfa_fcs_port_scn_send_scr(scn, NULL); break; case SCNSM_EVENT_PORT_OFFLINE: break; default: bfa_sm_fault(scn->port->fcs, event); } } static void bfa_fcs_port_scn_sm_sending_scr(struct bfa_fcs_port_scn_s *scn, enum port_scn_event event) { switch (event) { case SCNSM_EVENT_SCR_SENT: bfa_sm_set_state(scn, bfa_fcs_port_scn_sm_scr); break; case SCNSM_EVENT_PORT_OFFLINE: bfa_sm_set_state(scn, bfa_fcs_port_scn_sm_offline); bfa_fcxp_walloc_cancel(scn->port->fcs->bfa, &scn->fcxp_wqe); break; default: bfa_sm_fault(scn->port->fcs, event); } } static void bfa_fcs_port_scn_sm_scr(struct bfa_fcs_port_scn_s *scn, enum port_scn_event event) { struct bfa_fcs_port_s *port = scn->port; switch (event) { case SCNSM_EVENT_RSP_OK: bfa_sm_set_state(scn, bfa_fcs_port_scn_sm_online); break; case SCNSM_EVENT_RSP_ERROR: bfa_sm_set_state(scn, bfa_fcs_port_scn_sm_scr_retry); bfa_timer_start(port->fcs->bfa, &scn->timer, bfa_fcs_port_scn_timeout, scn, BFA_FCS_RETRY_TIMEOUT); break; case SCNSM_EVENT_PORT_OFFLINE: bfa_sm_set_state(scn, bfa_fcs_port_scn_sm_offline); bfa_fcxp_discard(scn->fcxp); break; default: bfa_sm_fault(scn->port->fcs, event); } } static void bfa_fcs_port_scn_sm_scr_retry(struct bfa_fcs_port_scn_s *scn, enum port_scn_event event) { switch (event) { case SCNSM_EVENT_TIMEOUT: bfa_sm_set_state(scn, bfa_fcs_port_scn_sm_sending_scr); bfa_fcs_port_scn_send_scr(scn, NULL); break; case SCNSM_EVENT_PORT_OFFLINE: bfa_sm_set_state(scn, bfa_fcs_port_scn_sm_offline); bfa_timer_stop(&scn->timer); break; default: bfa_sm_fault(scn->port->fcs, event); } } static void bfa_fcs_port_scn_sm_online(struct bfa_fcs_port_scn_s *scn, enum port_scn_event event) { switch (event) { case SCNSM_EVENT_PORT_OFFLINE: bfa_sm_set_state(scn, bfa_fcs_port_scn_sm_offline); break; default: bfa_sm_fault(scn->port->fcs, event); } } /** * fcs_scn_private FCS SCN private functions */ /** * This routine will be called to send a SCR command. */ static void bfa_fcs_port_scn_send_scr(void *scn_cbarg, struct bfa_fcxp_s *fcxp_alloced) { struct bfa_fcs_port_scn_s *scn = scn_cbarg; struct bfa_fcs_port_s *port = scn->port; struct fchs_s fchs; int len; struct bfa_fcxp_s *fcxp; bfa_trc(port->fcs, port->pid); bfa_trc(port->fcs, port->port_cfg.pwwn); fcxp = fcxp_alloced ? fcxp_alloced : bfa_fcs_fcxp_alloc(port->fcs); if (!fcxp) { bfa_fcxp_alloc_wait(port->fcs->bfa, &scn->fcxp_wqe, bfa_fcs_port_scn_send_scr, scn); return; } scn->fcxp = fcxp; /* * Handle VU registrations for Base port only */ if ((!port->vport) && bfa_ioc_get_fcmode(&port->fcs->bfa->ioc)) { len = fc_scr_build(&fchs, bfa_fcxp_get_reqbuf(fcxp), bfa_lps_is_brcd_fabric(port->fabric->lps), port->pid, 0); } else { len = fc_scr_build(&fchs, bfa_fcxp_get_reqbuf(fcxp), BFA_FALSE, port->pid, 0); } bfa_fcxp_send(fcxp, NULL, port->fabric->vf_id, port->lp_tag, BFA_FALSE, FC_CLASS_3, len, &fchs, bfa_fcs_port_scn_scr_response, (void *)scn, FC_MAX_PDUSZ, FC_ELS_TOV); bfa_sm_send_event(scn, SCNSM_EVENT_SCR_SENT); } static void bfa_fcs_port_scn_scr_response(void *fcsarg, struct bfa_fcxp_s *fcxp, void *cbarg, bfa_status_t req_status, u32 rsp_len, u32 resid_len, struct fchs_s *rsp_fchs) { struct bfa_fcs_port_scn_s *scn = (struct bfa_fcs_port_scn_s *)cbarg; struct bfa_fcs_port_s *port = scn->port; struct fc_els_cmd_s *els_cmd; struct fc_ls_rjt_s *ls_rjt; bfa_trc(port->fcs, port->port_cfg.pwwn); /* * Sanity Checks */ if (req_status != BFA_STATUS_OK) { bfa_trc(port->fcs, req_status); bfa_sm_send_event(scn, SCNSM_EVENT_RSP_ERROR); return; } els_cmd = (struct fc_els_cmd_s *) BFA_FCXP_RSP_PLD(fcxp); switch (els_cmd->els_code) { case FC_ELS_ACC: bfa_sm_send_event(scn, SCNSM_EVENT_RSP_OK); break; case FC_ELS_LS_RJT: ls_rjt = (struct fc_ls_rjt_s *) BFA_FCXP_RSP_PLD(fcxp); bfa_trc(port->fcs, ls_rjt->reason_code); bfa_trc(port->fcs, ls_rjt->reason_code_expl); bfa_sm_send_event(scn, SCNSM_EVENT_RSP_ERROR); break; default: bfa_sm_send_event(scn, SCNSM_EVENT_RSP_ERROR); } } /* * Send a LS Accept */ static void bfa_fcs_port_scn_send_ls_acc(struct bfa_fcs_port_s *port, struct fchs_s *rx_fchs) { struct fchs_s fchs; struct bfa_fcxp_s *fcxp; struct bfa_rport_s *bfa_rport = NULL; int len; bfa_trc(port->fcs, rx_fchs->s_id); fcxp = bfa_fcs_fcxp_alloc(port->fcs); if (!fcxp) return; len = fc_ls_acc_build(&fchs, bfa_fcxp_get_reqbuf(fcxp), rx_fchs->s_id, bfa_fcs_port_get_fcid(port), rx_fchs->ox_id); bfa_fcxp_send(fcxp, bfa_rport, port->fabric->vf_id, port->lp_tag, BFA_FALSE, FC_CLASS_3, len, &fchs, NULL, NULL, FC_MAX_PDUSZ, 0); } /** * This routine will be called by bfa_timer on timer timeouts. * * param[in] vport - pointer to bfa_fcs_port_t. * param[out] vport_status - pointer to return vport status in * * return * void * * Special Considerations: * * note */ static void bfa_fcs_port_scn_timeout(void *arg) { struct bfa_fcs_port_scn_s *scn = (struct bfa_fcs_port_scn_s *)arg; bfa_sm_send_event(scn, SCNSM_EVENT_TIMEOUT); } /** * fcs_scn_public FCS state change notification public interfaces */ /* * Functions called by port/fab */ void bfa_fcs_port_scn_init(struct bfa_fcs_port_s *port) { struct bfa_fcs_port_scn_s *scn = BFA_FCS_GET_SCN_FROM_PORT(port); scn->port = port; bfa_sm_set_state(scn, bfa_fcs_port_scn_sm_offline); } void bfa_fcs_port_scn_offline(struct bfa_fcs_port_s *port) { struct bfa_fcs_port_scn_s *scn = BFA_FCS_GET_SCN_FROM_PORT(port); scn->port = port; bfa_sm_send_event(scn, SCNSM_EVENT_PORT_OFFLINE); } void bfa_fcs_port_scn_online(struct bfa_fcs_port_s *port) { struct bfa_fcs_port_scn_s *scn = BFA_FCS_GET_SCN_FROM_PORT(port); scn->port = port; bfa_sm_send_event(scn, SCNSM_EVENT_PORT_ONLINE); } static void bfa_fcs_port_scn_portid_rscn(struct bfa_fcs_port_s *port, u32 rpid) { struct bfa_fcs_rport_s *rport; bfa_trc(port->fcs, rpid); /** * If this is an unknown device, then it just came online. * Otherwise let rport handle the RSCN event. */ rport = bfa_fcs_port_get_rport_by_pid(port, rpid); if (rport == NULL) { /* * If min cfg mode is enabled, we donot need to * discover any new rports. */ if (!__fcs_min_cfg(port->fcs)) rport = bfa_fcs_rport_create(port, rpid); } else { bfa_fcs_rport_scn(rport); } } /** * rscn format based PID comparison */ #define __fc_pid_match(__c0, __c1, __fmt) \ (((__fmt) == FC_RSCN_FORMAT_FABRIC) || \ (((__fmt) == FC_RSCN_FORMAT_DOMAIN) && \ ((__c0)[0] == (__c1)[0])) || \ (((__fmt) == FC_RSCN_FORMAT_AREA) && \ ((__c0)[0] == (__c1)[0]) && \ ((__c0)[1] == (__c1)[1]))) static void bfa_fcs_port_scn_multiport_rscn(struct bfa_fcs_port_s *port, enum fc_rscn_format format, u32 rscn_pid) { struct bfa_fcs_rport_s *rport; struct list_head *qe, *qe_next; u8 *c0, *c1; bfa_trc(port->fcs, format); bfa_trc(port->fcs, rscn_pid); c0 = (u8 *) &rscn_pid; list_for_each_safe(qe, qe_next, &port->rport_q) { rport = (struct bfa_fcs_rport_s *)qe; c1 = (u8 *) &rport->pid; if (__fc_pid_match(c0, c1, format)) bfa_fcs_rport_scn(rport); } } void bfa_fcs_port_scn_process_rscn(struct bfa_fcs_port_s *port, struct fchs_s *fchs, u32 len) { struct fc_rscn_pl_s *rscn = (struct fc_rscn_pl_s *) (fchs + 1); int num_entries; u32 rscn_pid; bfa_boolean_t nsquery = BFA_FALSE; int i = 0; num_entries = (bfa_os_ntohs(rscn->payldlen) - sizeof(u32)) / sizeof(rscn->event[0]); bfa_trc(port->fcs, num_entries); port->stats.num_rscn++; bfa_fcs_port_scn_send_ls_acc(port, fchs); for (i = 0; i < num_entries; i++) { rscn_pid = rscn->event[i].portid; bfa_trc(port->fcs, rscn->event[i].format); bfa_trc(port->fcs, rscn_pid); switch (rscn->event[i].format) { case FC_RSCN_FORMAT_PORTID: if (rscn->event[i].qualifier == FC_QOS_RSCN_EVENT) { /* * Ignore this event. f/w would have processed * it */ bfa_trc(port->fcs, rscn_pid); } else { port->stats.num_portid_rscn++; bfa_fcs_port_scn_portid_rscn(port, rscn_pid); } break; case FC_RSCN_FORMAT_FABRIC: if (rscn->event[i].qualifier == FC_FABRIC_NAME_RSCN_EVENT) { bfa_fcs_port_ms_fabric_rscn(port); break; } /* * !!!!!!!!! Fall Through !!!!!!!!!!!!! */ case FC_RSCN_FORMAT_AREA: case FC_RSCN_FORMAT_DOMAIN: nsquery = BFA_TRUE; bfa_fcs_port_scn_multiport_rscn(port, rscn->event[i].format, rscn_pid); break; default: bfa_assert(0); nsquery = BFA_TRUE; } } /** * If any of area, domain or fabric RSCN is received, do a fresh discovery * to find new devices. */ if (nsquery) bfa_fcs_port_ns_query(port); }