| /* |
| * 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 <bfa.h> |
| #include <bfa_svc.h> |
| #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_assert(0); |
| } |
| } |
| |
| 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_assert(0); |
| } |
| } |
| |
| 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_assert(0); |
| } |
| } |
| |
| 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_assert(0); |
| } |
| } |
| |
| 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_assert(0); |
| } |
| } |
| |
| |
| |
| /** |
| * 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_RA_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); |
| } |
| |
| |