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