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