| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2021 Broadcom. All Rights Reserved. The term | |
| * “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. | |
| */ | |
| /* | |
| * device_sm Node State Machine: Remote Device States | |
| */ | |
| #include "efc.h" | |
| #include "efc_device.h" | |
| #include "efc_fabric.h" | |
| void | |
| efc_d_send_prli_rsp(struct efc_node *node, u16 ox_id) | |
| { | |
| int rc = EFC_SCSI_CALL_COMPLETE; | |
| struct efc *efc = node->efc; | |
| node->ls_acc_oxid = ox_id; | |
| node->send_ls_acc = EFC_NODE_SEND_LS_ACC_PRLI; | |
| /* | |
| * Wait for backend session registration | |
| * to complete before sending PRLI resp | |
| */ | |
| if (node->init) { | |
| efc_log_info(efc, "[%s] found(initiator) WWPN:%s WWNN:%s\n", | |
| node->display_name, node->wwpn, node->wwnn); | |
| if (node->nport->enable_tgt) | |
| rc = efc->tt.scsi_new_node(efc, node); | |
| } | |
| if (rc < 0) | |
| efc_node_post_event(node, EFC_EVT_NODE_SESS_REG_FAIL, NULL); | |
| if (rc == EFC_SCSI_CALL_COMPLETE) | |
| efc_node_post_event(node, EFC_EVT_NODE_SESS_REG_OK, NULL); | |
| } | |
| static void | |
| __efc_d_common(const char *funcname, struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node *node = NULL; | |
| struct efc *efc = NULL; | |
| node = ctx->app; | |
| efc = node->efc; | |
| switch (evt) { | |
| /* Handle shutdown events */ | |
| case EFC_EVT_SHUTDOWN: | |
| efc_log_debug(efc, "[%s] %-20s %-20s\n", node->display_name, | |
| funcname, efc_sm_event_name(evt)); | |
| node->shutdown_reason = EFC_NODE_SHUTDOWN_DEFAULT; | |
| efc_node_transition(node, __efc_d_initiate_shutdown, NULL); | |
| break; | |
| case EFC_EVT_SHUTDOWN_EXPLICIT_LOGO: | |
| efc_log_debug(efc, "[%s] %-20s %-20s\n", | |
| node->display_name, funcname, | |
| efc_sm_event_name(evt)); | |
| node->shutdown_reason = EFC_NODE_SHUTDOWN_EXPLICIT_LOGO; | |
| efc_node_transition(node, __efc_d_initiate_shutdown, NULL); | |
| break; | |
| case EFC_EVT_SHUTDOWN_IMPLICIT_LOGO: | |
| efc_log_debug(efc, "[%s] %-20s %-20s\n", node->display_name, | |
| funcname, efc_sm_event_name(evt)); | |
| node->shutdown_reason = EFC_NODE_SHUTDOWN_IMPLICIT_LOGO; | |
| efc_node_transition(node, __efc_d_initiate_shutdown, NULL); | |
| break; | |
| default: | |
| /* call default event handler common to all nodes */ | |
| __efc_node_common(funcname, ctx, evt, arg); | |
| } | |
| } | |
| static void | |
| __efc_d_wait_del_node(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| /* | |
| * State is entered when a node sends a delete initiator/target call | |
| * to the target-server/initiator-client and needs to wait for that | |
| * work to complete. | |
| */ | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| efc_node_hold_frames(node); | |
| fallthrough; | |
| case EFC_EVT_NODE_ACTIVE_IO_LIST_EMPTY: | |
| case EFC_EVT_ALL_CHILD_NODES_FREE: | |
| /* These are expected events. */ | |
| break; | |
| case EFC_EVT_NODE_DEL_INI_COMPLETE: | |
| case EFC_EVT_NODE_DEL_TGT_COMPLETE: | |
| /* | |
| * node has either been detached or is in the process | |
| * of being detached, | |
| * call common node's initiate cleanup function | |
| */ | |
| efc_node_initiate_cleanup(node); | |
| break; | |
| case EFC_EVT_EXIT: | |
| efc_node_accept_frames(node); | |
| break; | |
| case EFC_EVT_SRRS_ELS_REQ_FAIL: | |
| /* Can happen as ELS IO IO's complete */ | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| break; | |
| /* ignore shutdown events as we're already in shutdown path */ | |
| case EFC_EVT_SHUTDOWN: | |
| /* have default shutdown event take precedence */ | |
| node->shutdown_reason = EFC_NODE_SHUTDOWN_DEFAULT; | |
| fallthrough; | |
| case EFC_EVT_SHUTDOWN_EXPLICIT_LOGO: | |
| case EFC_EVT_SHUTDOWN_IMPLICIT_LOGO: | |
| node_printf(node, "%s received\n", efc_sm_event_name(evt)); | |
| break; | |
| case EFC_EVT_DOMAIN_ATTACH_OK: | |
| /* don't care about domain_attach_ok */ | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| static void | |
| __efc_d_wait_del_ini_tgt(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| efc_node_hold_frames(node); | |
| fallthrough; | |
| case EFC_EVT_NODE_ACTIVE_IO_LIST_EMPTY: | |
| case EFC_EVT_ALL_CHILD_NODES_FREE: | |
| /* These are expected events. */ | |
| break; | |
| case EFC_EVT_NODE_DEL_INI_COMPLETE: | |
| case EFC_EVT_NODE_DEL_TGT_COMPLETE: | |
| efc_node_transition(node, __efc_d_wait_del_node, NULL); | |
| break; | |
| case EFC_EVT_EXIT: | |
| efc_node_accept_frames(node); | |
| break; | |
| case EFC_EVT_SRRS_ELS_REQ_FAIL: | |
| /* Can happen as ELS IO IO's complete */ | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| break; | |
| /* ignore shutdown events as we're already in shutdown path */ | |
| case EFC_EVT_SHUTDOWN: | |
| /* have default shutdown event take precedence */ | |
| node->shutdown_reason = EFC_NODE_SHUTDOWN_DEFAULT; | |
| fallthrough; | |
| case EFC_EVT_SHUTDOWN_EXPLICIT_LOGO: | |
| case EFC_EVT_SHUTDOWN_IMPLICIT_LOGO: | |
| node_printf(node, "%s received\n", efc_sm_event_name(evt)); | |
| break; | |
| case EFC_EVT_DOMAIN_ATTACH_OK: | |
| /* don't care about domain_attach_ok */ | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_initiate_shutdown(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node *node = ctx->app; | |
| struct efc *efc = node->efc; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: { | |
| int rc = EFC_SCSI_CALL_COMPLETE; | |
| /* assume no wait needed */ | |
| node->els_io_enabled = false; | |
| /* make necessary delete upcall(s) */ | |
| if (node->init && !node->targ) { | |
| efc_log_info(node->efc, | |
| "[%s] delete (initiator) WWPN %s WWNN %s\n", | |
| node->display_name, | |
| node->wwpn, node->wwnn); | |
| efc_node_transition(node, | |
| __efc_d_wait_del_node, | |
| NULL); | |
| if (node->nport->enable_tgt) | |
| rc = efc->tt.scsi_del_node(efc, node, | |
| EFC_SCSI_INITIATOR_DELETED); | |
| if (rc == EFC_SCSI_CALL_COMPLETE || rc < 0) | |
| efc_node_post_event(node, | |
| EFC_EVT_NODE_DEL_INI_COMPLETE, NULL); | |
| } else if (node->targ && !node->init) { | |
| efc_log_info(node->efc, | |
| "[%s] delete (target) WWPN %s WWNN %s\n", | |
| node->display_name, | |
| node->wwpn, node->wwnn); | |
| efc_node_transition(node, | |
| __efc_d_wait_del_node, | |
| NULL); | |
| if (node->nport->enable_ini) | |
| rc = efc->tt.scsi_del_node(efc, node, | |
| EFC_SCSI_TARGET_DELETED); | |
| if (rc == EFC_SCSI_CALL_COMPLETE) | |
| efc_node_post_event(node, | |
| EFC_EVT_NODE_DEL_TGT_COMPLETE, NULL); | |
| } else if (node->init && node->targ) { | |
| efc_log_info(node->efc, | |
| "[%s] delete (I+T) WWPN %s WWNN %s\n", | |
| node->display_name, node->wwpn, node->wwnn); | |
| efc_node_transition(node, __efc_d_wait_del_ini_tgt, | |
| NULL); | |
| if (node->nport->enable_tgt) | |
| rc = efc->tt.scsi_del_node(efc, node, | |
| EFC_SCSI_INITIATOR_DELETED); | |
| if (rc == EFC_SCSI_CALL_COMPLETE) | |
| efc_node_post_event(node, | |
| EFC_EVT_NODE_DEL_INI_COMPLETE, NULL); | |
| /* assume no wait needed */ | |
| rc = EFC_SCSI_CALL_COMPLETE; | |
| if (node->nport->enable_ini) | |
| rc = efc->tt.scsi_del_node(efc, node, | |
| EFC_SCSI_TARGET_DELETED); | |
| if (rc == EFC_SCSI_CALL_COMPLETE) | |
| efc_node_post_event(node, | |
| EFC_EVT_NODE_DEL_TGT_COMPLETE, NULL); | |
| } | |
| /* we've initiated the upcalls as needed, now kick off the node | |
| * detach to precipitate the aborting of outstanding exchanges | |
| * associated with said node | |
| * | |
| * Beware: if we've made upcall(s), we've already transitioned | |
| * to a new state by the time we execute this. | |
| * consider doing this before the upcalls? | |
| */ | |
| if (node->attached) { | |
| /* issue hw node free; don't care if succeeds right | |
| * away or sometime later, will check node->attached | |
| * later in shutdown process | |
| */ | |
| rc = efc_cmd_node_detach(efc, &node->rnode); | |
| if (rc < 0) | |
| node_printf(node, | |
| "Failed freeing HW node, rc=%d\n", | |
| rc); | |
| } | |
| /* if neither initiator nor target, proceed to cleanup */ | |
| if (!node->init && !node->targ) { | |
| /* | |
| * node has either been detached or is in | |
| * the process of being detached, | |
| * call common node's initiate cleanup function | |
| */ | |
| efc_node_initiate_cleanup(node); | |
| } | |
| break; | |
| } | |
| case EFC_EVT_ALL_CHILD_NODES_FREE: | |
| /* Ignore, this can happen if an ELS is | |
| * aborted while in a delay/retry state | |
| */ | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_wait_loop(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| efc_node_hold_frames(node); | |
| break; | |
| case EFC_EVT_EXIT: | |
| efc_node_accept_frames(node); | |
| break; | |
| case EFC_EVT_DOMAIN_ATTACH_OK: { | |
| /* send PLOGI automatically if initiator */ | |
| efc_node_init_device(node, true); | |
| break; | |
| } | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| efc_send_ls_acc_after_attach(struct efc_node *node, | |
| struct fc_frame_header *hdr, | |
| enum efc_node_send_ls_acc ls) | |
| { | |
| u16 ox_id = be16_to_cpu(hdr->fh_ox_id); | |
| /* Save the OX_ID for sending LS_ACC sometime later */ | |
| WARN_ON(node->send_ls_acc != EFC_NODE_SEND_LS_ACC_NONE); | |
| node->ls_acc_oxid = ox_id; | |
| node->send_ls_acc = ls; | |
| node->ls_acc_did = ntoh24(hdr->fh_d_id); | |
| } | |
| void | |
| efc_process_prli_payload(struct efc_node *node, void *prli) | |
| { | |
| struct { | |
| struct fc_els_prli prli; | |
| struct fc_els_spp sp; | |
| } *pp; | |
| pp = prli; | |
| node->init = (pp->sp.spp_flags & FCP_SPPF_INIT_FCN) != 0; | |
| node->targ = (pp->sp.spp_flags & FCP_SPPF_TARG_FCN) != 0; | |
| } | |
| void | |
| __efc_d_wait_plogi_acc_cmpl(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| efc_node_hold_frames(node); | |
| break; | |
| case EFC_EVT_EXIT: | |
| efc_node_accept_frames(node); | |
| break; | |
| case EFC_EVT_SRRS_ELS_CMPL_FAIL: | |
| WARN_ON(!node->els_cmpl_cnt); | |
| node->els_cmpl_cnt--; | |
| node->shutdown_reason = EFC_NODE_SHUTDOWN_DEFAULT; | |
| efc_node_transition(node, __efc_d_initiate_shutdown, NULL); | |
| break; | |
| case EFC_EVT_SRRS_ELS_CMPL_OK: /* PLOGI ACC completions */ | |
| WARN_ON(!node->els_cmpl_cnt); | |
| node->els_cmpl_cnt--; | |
| efc_node_transition(node, __efc_d_port_logged_in, NULL); | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_wait_logo_rsp(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| efc_node_hold_frames(node); | |
| break; | |
| case EFC_EVT_EXIT: | |
| efc_node_accept_frames(node); | |
| break; | |
| case EFC_EVT_SRRS_ELS_REQ_OK: | |
| case EFC_EVT_SRRS_ELS_REQ_RJT: | |
| case EFC_EVT_SRRS_ELS_REQ_FAIL: | |
| /* LOGO response received, sent shutdown */ | |
| if (efc_node_check_els_req(ctx, evt, arg, ELS_LOGO, | |
| __efc_d_common, __func__)) | |
| return; | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| node_printf(node, | |
| "LOGO sent (evt=%s), shutdown node\n", | |
| efc_sm_event_name(evt)); | |
| /* sm: / post explicit logout */ | |
| efc_node_post_event(node, EFC_EVT_SHUTDOWN_EXPLICIT_LOGO, | |
| NULL); | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| efc_node_init_device(struct efc_node *node, bool send_plogi) | |
| { | |
| node->send_plogi = send_plogi; | |
| if ((node->efc->nodedb_mask & EFC_NODEDB_PAUSE_NEW_NODES) && | |
| (node->rnode.fc_id != FC_FID_DOM_MGR)) { | |
| node->nodedb_state = __efc_d_init; | |
| efc_node_transition(node, __efc_node_paused, NULL); | |
| } else { | |
| efc_node_transition(node, __efc_d_init, NULL); | |
| } | |
| } | |
| static void | |
| efc_d_check_plogi_topology(struct efc_node *node, u32 d_id) | |
| { | |
| switch (node->nport->topology) { | |
| case EFC_NPORT_TOPO_P2P: | |
| /* we're not attached and nport is p2p, | |
| * need to attach | |
| */ | |
| efc_domain_attach(node->nport->domain, d_id); | |
| efc_node_transition(node, __efc_d_wait_domain_attach, NULL); | |
| break; | |
| case EFC_NPORT_TOPO_FABRIC: | |
| /* we're not attached and nport is fabric, domain | |
| * attach should have already been requested as part | |
| * of the fabric state machine, wait for it | |
| */ | |
| efc_node_transition(node, __efc_d_wait_domain_attach, NULL); | |
| break; | |
| case EFC_NPORT_TOPO_UNKNOWN: | |
| /* Two possibilities: | |
| * 1. received a PLOGI before our FLOGI has completed | |
| * (possible since completion comes in on another | |
| * CQ), thus we don't know what we're connected to | |
| * yet; transition to a state to wait for the | |
| * fabric node to tell us; | |
| * 2. PLOGI received before link went down and we | |
| * haven't performed domain attach yet. | |
| * Note: we cannot distinguish between 1. and 2. | |
| * so have to assume PLOGI | |
| * was received after link back up. | |
| */ | |
| node_printf(node, "received PLOGI, unknown topology did=0x%x\n", | |
| d_id); | |
| efc_node_transition(node, __efc_d_wait_topology_notify, NULL); | |
| break; | |
| default: | |
| node_printf(node, "received PLOGI, unexpected topology %d\n", | |
| node->nport->topology); | |
| } | |
| } | |
| void | |
| __efc_d_init(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node_cb *cbdata = arg; | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| /* | |
| * This state is entered when a node is instantiated, | |
| * either having been discovered from a name services query, | |
| * or having received a PLOGI/FLOGI. | |
| */ | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| if (!node->send_plogi) | |
| break; | |
| /* only send if we have initiator capability, | |
| * and domain is attached | |
| */ | |
| if (node->nport->enable_ini && | |
| node->nport->domain->attached) { | |
| efc_send_plogi(node); | |
| efc_node_transition(node, __efc_d_wait_plogi_rsp, NULL); | |
| } else { | |
| node_printf(node, "not sending plogi nport.ini=%d,", | |
| node->nport->enable_ini); | |
| node_printf(node, "domain attached=%d\n", | |
| node->nport->domain->attached); | |
| } | |
| break; | |
| case EFC_EVT_PLOGI_RCVD: { | |
| /* T, or I+T */ | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| int rc; | |
| efc_node_save_sparms(node, cbdata->payload->dma.virt); | |
| efc_send_ls_acc_after_attach(node, | |
| cbdata->header->dma.virt, | |
| EFC_NODE_SEND_LS_ACC_PLOGI); | |
| /* domain not attached; several possibilities: */ | |
| if (!node->nport->domain->attached) { | |
| efc_d_check_plogi_topology(node, ntoh24(hdr->fh_d_id)); | |
| break; | |
| } | |
| /* domain already attached */ | |
| rc = efc_node_attach(node); | |
| efc_node_transition(node, __efc_d_wait_node_attach, NULL); | |
| if (rc < 0) | |
| efc_node_post_event(node, EFC_EVT_NODE_ATTACH_FAIL, NULL); | |
| break; | |
| } | |
| case EFC_EVT_FDISC_RCVD: { | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| break; | |
| } | |
| case EFC_EVT_FLOGI_RCVD: { | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| u32 d_id = ntoh24(hdr->fh_d_id); | |
| /* sm: / save sparams, send FLOGI acc */ | |
| memcpy(node->nport->domain->flogi_service_params, | |
| cbdata->payload->dma.virt, | |
| sizeof(struct fc_els_flogi)); | |
| /* send FC LS_ACC response, override s_id */ | |
| efc_fabric_set_topology(node, EFC_NPORT_TOPO_P2P); | |
| efc_send_flogi_p2p_acc(node, be16_to_cpu(hdr->fh_ox_id), d_id); | |
| if (efc_p2p_setup(node->nport)) { | |
| node_printf(node, "p2p failed, shutting down node\n"); | |
| efc_node_post_event(node, EFC_EVT_SHUTDOWN, NULL); | |
| break; | |
| } | |
| efc_node_transition(node, __efc_p2p_wait_flogi_acc_cmpl, NULL); | |
| break; | |
| } | |
| case EFC_EVT_LOGO_RCVD: { | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| if (!node->nport->domain->attached) { | |
| /* most likely a frame left over from before a link | |
| * down; drop and | |
| * shut node down w/ "explicit logout" so pending | |
| * frames are processed | |
| */ | |
| node_printf(node, "%s domain not attached, dropping\n", | |
| efc_sm_event_name(evt)); | |
| efc_node_post_event(node, | |
| EFC_EVT_SHUTDOWN_EXPLICIT_LOGO, NULL); | |
| break; | |
| } | |
| efc_send_logo_acc(node, be16_to_cpu(hdr->fh_ox_id)); | |
| efc_node_transition(node, __efc_d_wait_logo_acc_cmpl, NULL); | |
| break; | |
| } | |
| case EFC_EVT_PRLI_RCVD: | |
| case EFC_EVT_PRLO_RCVD: | |
| case EFC_EVT_PDISC_RCVD: | |
| case EFC_EVT_ADISC_RCVD: | |
| case EFC_EVT_RSCN_RCVD: { | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| if (!node->nport->domain->attached) { | |
| /* most likely a frame left over from before a link | |
| * down; drop and shut node down w/ "explicit logout" | |
| * so pending frames are processed | |
| */ | |
| node_printf(node, "%s domain not attached, dropping\n", | |
| efc_sm_event_name(evt)); | |
| efc_node_post_event(node, | |
| EFC_EVT_SHUTDOWN_EXPLICIT_LOGO, | |
| NULL); | |
| break; | |
| } | |
| node_printf(node, "%s received, sending reject\n", | |
| efc_sm_event_name(evt)); | |
| efc_send_ls_rjt(node, be16_to_cpu(hdr->fh_ox_id), | |
| ELS_RJT_UNAB, ELS_EXPL_PLOGI_REQD, 0); | |
| break; | |
| } | |
| case EFC_EVT_FCP_CMD_RCVD: { | |
| /* note: problem, we're now expecting an ELS REQ completion | |
| * from both the LOGO and PLOGI | |
| */ | |
| if (!node->nport->domain->attached) { | |
| /* most likely a frame left over from before a | |
| * link down; drop and | |
| * shut node down w/ "explicit logout" so pending | |
| * frames are processed | |
| */ | |
| node_printf(node, "%s domain not attached, dropping\n", | |
| efc_sm_event_name(evt)); | |
| efc_node_post_event(node, | |
| EFC_EVT_SHUTDOWN_EXPLICIT_LOGO, | |
| NULL); | |
| break; | |
| } | |
| /* Send LOGO */ | |
| node_printf(node, "FCP_CMND received, send LOGO\n"); | |
| if (efc_send_logo(node)) { | |
| /* | |
| * failed to send LOGO, go ahead and cleanup node | |
| * anyways | |
| */ | |
| node_printf(node, "Failed to send LOGO\n"); | |
| efc_node_post_event(node, | |
| EFC_EVT_SHUTDOWN_EXPLICIT_LOGO, | |
| NULL); | |
| } else { | |
| /* sent LOGO, wait for response */ | |
| efc_node_transition(node, | |
| __efc_d_wait_logo_rsp, NULL); | |
| } | |
| break; | |
| } | |
| case EFC_EVT_DOMAIN_ATTACH_OK: | |
| /* don't care about domain_attach_ok */ | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_wait_plogi_rsp(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| int rc; | |
| struct efc_node_cb *cbdata = arg; | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_PLOGI_RCVD: { | |
| /* T, or I+T */ | |
| /* received PLOGI with svc parms, go ahead and attach node | |
| * when PLOGI that was sent ultimately completes, it'll be a | |
| * no-op | |
| * | |
| * If there is an outstanding PLOGI sent, can we set a flag | |
| * to indicate that we don't want to retry it if it times out? | |
| */ | |
| efc_node_save_sparms(node, cbdata->payload->dma.virt); | |
| efc_send_ls_acc_after_attach(node, | |
| cbdata->header->dma.virt, | |
| EFC_NODE_SEND_LS_ACC_PLOGI); | |
| /* sm: domain->attached / efc_node_attach */ | |
| rc = efc_node_attach(node); | |
| efc_node_transition(node, __efc_d_wait_node_attach, NULL); | |
| if (rc < 0) | |
| efc_node_post_event(node, | |
| EFC_EVT_NODE_ATTACH_FAIL, NULL); | |
| break; | |
| } | |
| case EFC_EVT_PRLI_RCVD: | |
| /* I, or I+T */ | |
| /* sent PLOGI and before completion was seen, received the | |
| * PRLI from the remote node (WCQEs and RCQEs come in on | |
| * different queues and order of processing cannot be assumed) | |
| * Save OXID so PRLI can be sent after the attach and continue | |
| * to wait for PLOGI response | |
| */ | |
| efc_process_prli_payload(node, cbdata->payload->dma.virt); | |
| efc_send_ls_acc_after_attach(node, | |
| cbdata->header->dma.virt, | |
| EFC_NODE_SEND_LS_ACC_PRLI); | |
| efc_node_transition(node, __efc_d_wait_plogi_rsp_recvd_prli, | |
| NULL); | |
| break; | |
| case EFC_EVT_LOGO_RCVD: /* why don't we do a shutdown here?? */ | |
| case EFC_EVT_PRLO_RCVD: | |
| case EFC_EVT_PDISC_RCVD: | |
| case EFC_EVT_FDISC_RCVD: | |
| case EFC_EVT_ADISC_RCVD: | |
| case EFC_EVT_RSCN_RCVD: | |
| case EFC_EVT_SCR_RCVD: { | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| node_printf(node, "%s received, sending reject\n", | |
| efc_sm_event_name(evt)); | |
| efc_send_ls_rjt(node, be16_to_cpu(hdr->fh_ox_id), | |
| ELS_RJT_UNAB, ELS_EXPL_PLOGI_REQD, 0); | |
| break; | |
| } | |
| case EFC_EVT_SRRS_ELS_REQ_OK: /* PLOGI response received */ | |
| /* Completion from PLOGI sent */ | |
| if (efc_node_check_els_req(ctx, evt, arg, ELS_PLOGI, | |
| __efc_d_common, __func__)) | |
| return; | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| /* sm: / save sparams, efc_node_attach */ | |
| efc_node_save_sparms(node, cbdata->els_rsp.virt); | |
| rc = efc_node_attach(node); | |
| efc_node_transition(node, __efc_d_wait_node_attach, NULL); | |
| if (rc < 0) | |
| efc_node_post_event(node, | |
| EFC_EVT_NODE_ATTACH_FAIL, NULL); | |
| break; | |
| case EFC_EVT_SRRS_ELS_REQ_FAIL: /* PLOGI response received */ | |
| /* PLOGI failed, shutdown the node */ | |
| if (efc_node_check_els_req(ctx, evt, arg, ELS_PLOGI, | |
| __efc_d_common, __func__)) | |
| return; | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| efc_node_post_event(node, EFC_EVT_SHUTDOWN, NULL); | |
| break; | |
| case EFC_EVT_SRRS_ELS_REQ_RJT: | |
| /* Our PLOGI was rejected, this is ok in some cases */ | |
| if (efc_node_check_els_req(ctx, evt, arg, ELS_PLOGI, | |
| __efc_d_common, __func__)) | |
| return; | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| break; | |
| case EFC_EVT_FCP_CMD_RCVD: { | |
| /* not logged in yet and outstanding PLOGI so don't send LOGO, | |
| * just drop | |
| */ | |
| node_printf(node, "FCP_CMND received, drop\n"); | |
| break; | |
| } | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_wait_plogi_rsp_recvd_prli(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| int rc; | |
| struct efc_node_cb *cbdata = arg; | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| /* | |
| * Since we've received a PRLI, we have a port login and will | |
| * just need to wait for the PLOGI response to do the node | |
| * attach and then we can send the LS_ACC for the PRLI. If, | |
| * during this time, we receive FCP_CMNDs (which is possible | |
| * since we've already sent a PRLI and our peer may have | |
| * accepted). At this time, we are not waiting on any other | |
| * unsolicited frames to continue with the login process. Thus, | |
| * it will not hurt to hold frames here. | |
| */ | |
| efc_node_hold_frames(node); | |
| break; | |
| case EFC_EVT_EXIT: | |
| efc_node_accept_frames(node); | |
| break; | |
| case EFC_EVT_SRRS_ELS_REQ_OK: /* PLOGI response received */ | |
| /* Completion from PLOGI sent */ | |
| if (efc_node_check_els_req(ctx, evt, arg, ELS_PLOGI, | |
| __efc_d_common, __func__)) | |
| return; | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| /* sm: / save sparams, efc_node_attach */ | |
| efc_node_save_sparms(node, cbdata->els_rsp.virt); | |
| rc = efc_node_attach(node); | |
| efc_node_transition(node, __efc_d_wait_node_attach, NULL); | |
| if (rc < 0) | |
| efc_node_post_event(node, EFC_EVT_NODE_ATTACH_FAIL, | |
| NULL); | |
| break; | |
| case EFC_EVT_SRRS_ELS_REQ_FAIL: /* PLOGI response received */ | |
| case EFC_EVT_SRRS_ELS_REQ_RJT: | |
| /* PLOGI failed, shutdown the node */ | |
| if (efc_node_check_els_req(ctx, evt, arg, ELS_PLOGI, | |
| __efc_d_common, __func__)) | |
| return; | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| efc_node_post_event(node, EFC_EVT_SHUTDOWN, NULL); | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_wait_domain_attach(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| int rc; | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| efc_node_hold_frames(node); | |
| break; | |
| case EFC_EVT_EXIT: | |
| efc_node_accept_frames(node); | |
| break; | |
| case EFC_EVT_DOMAIN_ATTACH_OK: | |
| WARN_ON(!node->nport->domain->attached); | |
| /* sm: / efc_node_attach */ | |
| rc = efc_node_attach(node); | |
| efc_node_transition(node, __efc_d_wait_node_attach, NULL); | |
| if (rc < 0) | |
| efc_node_post_event(node, EFC_EVT_NODE_ATTACH_FAIL, | |
| NULL); | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_wait_topology_notify(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| int rc; | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| efc_node_hold_frames(node); | |
| break; | |
| case EFC_EVT_EXIT: | |
| efc_node_accept_frames(node); | |
| break; | |
| case EFC_EVT_NPORT_TOPOLOGY_NOTIFY: { | |
| enum efc_nport_topology *topology = arg; | |
| WARN_ON(node->nport->domain->attached); | |
| WARN_ON(node->send_ls_acc != EFC_NODE_SEND_LS_ACC_PLOGI); | |
| node_printf(node, "topology notification, topology=%d\n", | |
| *topology); | |
| /* At the time the PLOGI was received, the topology was unknown, | |
| * so we didn't know which node would perform the domain attach: | |
| * 1. The node from which the PLOGI was sent (p2p) or | |
| * 2. The node to which the FLOGI was sent (fabric). | |
| */ | |
| if (*topology == EFC_NPORT_TOPO_P2P) { | |
| /* if this is p2p, need to attach to the domain using | |
| * the d_id from the PLOGI received | |
| */ | |
| efc_domain_attach(node->nport->domain, | |
| node->ls_acc_did); | |
| } | |
| /* else, if this is fabric, the domain attach | |
| * should be performed by the fabric node (node sending FLOGI); | |
| * just wait for attach to complete | |
| */ | |
| efc_node_transition(node, __efc_d_wait_domain_attach, NULL); | |
| break; | |
| } | |
| case EFC_EVT_DOMAIN_ATTACH_OK: | |
| WARN_ON(!node->nport->domain->attached); | |
| node_printf(node, "domain attach ok\n"); | |
| /* sm: / efc_node_attach */ | |
| rc = efc_node_attach(node); | |
| efc_node_transition(node, __efc_d_wait_node_attach, NULL); | |
| if (rc < 0) | |
| efc_node_post_event(node, | |
| EFC_EVT_NODE_ATTACH_FAIL, NULL); | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_wait_node_attach(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| efc_node_hold_frames(node); | |
| break; | |
| case EFC_EVT_EXIT: | |
| efc_node_accept_frames(node); | |
| break; | |
| case EFC_EVT_NODE_ATTACH_OK: | |
| node->attached = true; | |
| switch (node->send_ls_acc) { | |
| case EFC_NODE_SEND_LS_ACC_PLOGI: { | |
| /* sm: send_plogi_acc is set / send PLOGI acc */ | |
| /* Normal case for T, or I+T */ | |
| efc_send_plogi_acc(node, node->ls_acc_oxid); | |
| efc_node_transition(node, __efc_d_wait_plogi_acc_cmpl, | |
| NULL); | |
| node->send_ls_acc = EFC_NODE_SEND_LS_ACC_NONE; | |
| node->ls_acc_io = NULL; | |
| break; | |
| } | |
| case EFC_NODE_SEND_LS_ACC_PRLI: { | |
| efc_d_send_prli_rsp(node, node->ls_acc_oxid); | |
| node->send_ls_acc = EFC_NODE_SEND_LS_ACC_NONE; | |
| node->ls_acc_io = NULL; | |
| break; | |
| } | |
| case EFC_NODE_SEND_LS_ACC_NONE: | |
| default: | |
| /* Normal case for I */ | |
| /* sm: send_plogi_acc is not set / send PLOGI acc */ | |
| efc_node_transition(node, | |
| __efc_d_port_logged_in, NULL); | |
| break; | |
| } | |
| break; | |
| case EFC_EVT_NODE_ATTACH_FAIL: | |
| /* node attach failed, shutdown the node */ | |
| node->attached = false; | |
| node_printf(node, "node attach failed\n"); | |
| node->shutdown_reason = EFC_NODE_SHUTDOWN_DEFAULT; | |
| efc_node_transition(node, __efc_d_initiate_shutdown, NULL); | |
| break; | |
| /* Handle shutdown events */ | |
| case EFC_EVT_SHUTDOWN: | |
| node_printf(node, "%s received\n", efc_sm_event_name(evt)); | |
| node->shutdown_reason = EFC_NODE_SHUTDOWN_DEFAULT; | |
| efc_node_transition(node, __efc_d_wait_attach_evt_shutdown, | |
| NULL); | |
| break; | |
| case EFC_EVT_SHUTDOWN_EXPLICIT_LOGO: | |
| node_printf(node, "%s received\n", efc_sm_event_name(evt)); | |
| node->shutdown_reason = EFC_NODE_SHUTDOWN_EXPLICIT_LOGO; | |
| efc_node_transition(node, __efc_d_wait_attach_evt_shutdown, | |
| NULL); | |
| break; | |
| case EFC_EVT_SHUTDOWN_IMPLICIT_LOGO: | |
| node_printf(node, "%s received\n", efc_sm_event_name(evt)); | |
| node->shutdown_reason = EFC_NODE_SHUTDOWN_IMPLICIT_LOGO; | |
| efc_node_transition(node, | |
| __efc_d_wait_attach_evt_shutdown, NULL); | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_wait_attach_evt_shutdown(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| efc_node_hold_frames(node); | |
| break; | |
| case EFC_EVT_EXIT: | |
| efc_node_accept_frames(node); | |
| break; | |
| /* wait for any of these attach events and then shutdown */ | |
| case EFC_EVT_NODE_ATTACH_OK: | |
| node->attached = true; | |
| node_printf(node, "Attach evt=%s, proceed to shutdown\n", | |
| efc_sm_event_name(evt)); | |
| efc_node_transition(node, __efc_d_initiate_shutdown, NULL); | |
| break; | |
| case EFC_EVT_NODE_ATTACH_FAIL: | |
| /* node attach failed, shutdown the node */ | |
| node->attached = false; | |
| node_printf(node, "Attach evt=%s, proceed to shutdown\n", | |
| efc_sm_event_name(evt)); | |
| efc_node_transition(node, __efc_d_initiate_shutdown, NULL); | |
| break; | |
| /* ignore shutdown events as we're already in shutdown path */ | |
| case EFC_EVT_SHUTDOWN: | |
| /* have default shutdown event take precedence */ | |
| node->shutdown_reason = EFC_NODE_SHUTDOWN_DEFAULT; | |
| fallthrough; | |
| case EFC_EVT_SHUTDOWN_EXPLICIT_LOGO: | |
| case EFC_EVT_SHUTDOWN_IMPLICIT_LOGO: | |
| node_printf(node, "%s received\n", efc_sm_event_name(evt)); | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_port_logged_in(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node_cb *cbdata = arg; | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| /* Normal case for I or I+T */ | |
| if (node->nport->enable_ini && | |
| !(node->rnode.fc_id != FC_FID_DOM_MGR)) { | |
| /* sm: if enable_ini / send PRLI */ | |
| efc_send_prli(node); | |
| /* can now expect ELS_REQ_OK/FAIL/RJT */ | |
| } | |
| break; | |
| case EFC_EVT_FCP_CMD_RCVD: { | |
| break; | |
| } | |
| case EFC_EVT_PRLI_RCVD: { | |
| /* Normal case for T or I+T */ | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| struct { | |
| struct fc_els_prli prli; | |
| struct fc_els_spp sp; | |
| } *pp; | |
| pp = cbdata->payload->dma.virt; | |
| if (pp->sp.spp_type != FC_TYPE_FCP) { | |
| /*Only FCP is supported*/ | |
| efc_send_ls_rjt(node, be16_to_cpu(hdr->fh_ox_id), | |
| ELS_RJT_UNAB, ELS_EXPL_UNSUPR, 0); | |
| break; | |
| } | |
| efc_process_prli_payload(node, cbdata->payload->dma.virt); | |
| efc_d_send_prli_rsp(node, be16_to_cpu(hdr->fh_ox_id)); | |
| break; | |
| } | |
| case EFC_EVT_NODE_SESS_REG_OK: | |
| if (node->send_ls_acc == EFC_NODE_SEND_LS_ACC_PRLI) | |
| efc_send_prli_acc(node, node->ls_acc_oxid); | |
| node->send_ls_acc = EFC_NODE_SEND_LS_ACC_NONE; | |
| efc_node_transition(node, __efc_d_device_ready, NULL); | |
| break; | |
| case EFC_EVT_NODE_SESS_REG_FAIL: | |
| efc_send_ls_rjt(node, node->ls_acc_oxid, ELS_RJT_UNAB, | |
| ELS_EXPL_UNSUPR, 0); | |
| node->send_ls_acc = EFC_NODE_SEND_LS_ACC_NONE; | |
| break; | |
| case EFC_EVT_SRRS_ELS_REQ_OK: { /* PRLI response */ | |
| /* Normal case for I or I+T */ | |
| if (efc_node_check_els_req(ctx, evt, arg, ELS_PRLI, | |
| __efc_d_common, __func__)) | |
| return; | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| /* sm: / process PRLI payload */ | |
| efc_process_prli_payload(node, cbdata->els_rsp.virt); | |
| efc_node_transition(node, __efc_d_device_ready, NULL); | |
| break; | |
| } | |
| case EFC_EVT_SRRS_ELS_REQ_FAIL: { /* PRLI response failed */ | |
| /* I, I+T, assume some link failure, shutdown node */ | |
| if (efc_node_check_els_req(ctx, evt, arg, ELS_PRLI, | |
| __efc_d_common, __func__)) | |
| return; | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| efc_node_post_event(node, EFC_EVT_SHUTDOWN, NULL); | |
| break; | |
| } | |
| case EFC_EVT_SRRS_ELS_REQ_RJT: { | |
| /* PRLI rejected by remote | |
| * Normal for I, I+T (connected to an I) | |
| * Node doesn't want to be a target, stay here and wait for a | |
| * PRLI from the remote node | |
| * if it really wants to connect to us as target | |
| */ | |
| if (efc_node_check_els_req(ctx, evt, arg, ELS_PRLI, | |
| __efc_d_common, __func__)) | |
| return; | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| break; | |
| } | |
| case EFC_EVT_SRRS_ELS_CMPL_OK: { | |
| /* Normal T, I+T, target-server rejected the process login */ | |
| /* This would be received only in the case where we sent | |
| * LS_RJT for the PRLI, so | |
| * do nothing. (note: as T only we could shutdown the node) | |
| */ | |
| WARN_ON(!node->els_cmpl_cnt); | |
| node->els_cmpl_cnt--; | |
| break; | |
| } | |
| case EFC_EVT_PLOGI_RCVD: { | |
| /*sm: / save sparams, set send_plogi_acc, | |
| *post implicit logout | |
| * Save plogi parameters | |
| */ | |
| efc_node_save_sparms(node, cbdata->payload->dma.virt); | |
| efc_send_ls_acc_after_attach(node, | |
| cbdata->header->dma.virt, | |
| EFC_NODE_SEND_LS_ACC_PLOGI); | |
| /* Restart node attach with new service parameters, | |
| * and send ACC | |
| */ | |
| efc_node_post_event(node, EFC_EVT_SHUTDOWN_IMPLICIT_LOGO, | |
| NULL); | |
| break; | |
| } | |
| case EFC_EVT_LOGO_RCVD: { | |
| /* I, T, I+T */ | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| node_printf(node, "%s received attached=%d\n", | |
| efc_sm_event_name(evt), | |
| node->attached); | |
| /* sm: / send LOGO acc */ | |
| efc_send_logo_acc(node, be16_to_cpu(hdr->fh_ox_id)); | |
| efc_node_transition(node, __efc_d_wait_logo_acc_cmpl, NULL); | |
| break; | |
| } | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_wait_logo_acc_cmpl(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| efc_node_hold_frames(node); | |
| break; | |
| case EFC_EVT_EXIT: | |
| efc_node_accept_frames(node); | |
| break; | |
| case EFC_EVT_SRRS_ELS_CMPL_OK: | |
| case EFC_EVT_SRRS_ELS_CMPL_FAIL: | |
| /* sm: / post explicit logout */ | |
| WARN_ON(!node->els_cmpl_cnt); | |
| node->els_cmpl_cnt--; | |
| efc_node_post_event(node, | |
| EFC_EVT_SHUTDOWN_EXPLICIT_LOGO, NULL); | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_device_ready(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node_cb *cbdata = arg; | |
| struct efc_node *node = ctx->app; | |
| struct efc *efc = node->efc; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| if (evt != EFC_EVT_FCP_CMD_RCVD) | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: | |
| node->fcp_enabled = true; | |
| if (node->targ) { | |
| efc_log_info(efc, | |
| "[%s] found (target) WWPN %s WWNN %s\n", | |
| node->display_name, | |
| node->wwpn, node->wwnn); | |
| if (node->nport->enable_ini) | |
| efc->tt.scsi_new_node(efc, node); | |
| } | |
| break; | |
| case EFC_EVT_EXIT: | |
| node->fcp_enabled = false; | |
| break; | |
| case EFC_EVT_PLOGI_RCVD: { | |
| /* sm: / save sparams, set send_plogi_acc, post implicit | |
| * logout | |
| * Save plogi parameters | |
| */ | |
| efc_node_save_sparms(node, cbdata->payload->dma.virt); | |
| efc_send_ls_acc_after_attach(node, | |
| cbdata->header->dma.virt, | |
| EFC_NODE_SEND_LS_ACC_PLOGI); | |
| /* | |
| * Restart node attach with new service parameters, | |
| * and send ACC | |
| */ | |
| efc_node_post_event(node, | |
| EFC_EVT_SHUTDOWN_IMPLICIT_LOGO, NULL); | |
| break; | |
| } | |
| case EFC_EVT_PRLI_RCVD: { | |
| /* T, I+T: remote initiator is slow to get started */ | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| struct { | |
| struct fc_els_prli prli; | |
| struct fc_els_spp sp; | |
| } *pp; | |
| pp = cbdata->payload->dma.virt; | |
| if (pp->sp.spp_type != FC_TYPE_FCP) { | |
| /*Only FCP is supported*/ | |
| efc_send_ls_rjt(node, be16_to_cpu(hdr->fh_ox_id), | |
| ELS_RJT_UNAB, ELS_EXPL_UNSUPR, 0); | |
| break; | |
| } | |
| efc_process_prli_payload(node, cbdata->payload->dma.virt); | |
| efc_send_prli_acc(node, be16_to_cpu(hdr->fh_ox_id)); | |
| break; | |
| } | |
| case EFC_EVT_PRLO_RCVD: { | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| /* sm: / send PRLO acc */ | |
| efc_send_prlo_acc(node, be16_to_cpu(hdr->fh_ox_id)); | |
| /* need implicit logout? */ | |
| break; | |
| } | |
| case EFC_EVT_LOGO_RCVD: { | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| node_printf(node, "%s received attached=%d\n", | |
| efc_sm_event_name(evt), node->attached); | |
| /* sm: / send LOGO acc */ | |
| efc_send_logo_acc(node, be16_to_cpu(hdr->fh_ox_id)); | |
| efc_node_transition(node, __efc_d_wait_logo_acc_cmpl, NULL); | |
| break; | |
| } | |
| case EFC_EVT_ADISC_RCVD: { | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| /* sm: / send ADISC acc */ | |
| efc_send_adisc_acc(node, be16_to_cpu(hdr->fh_ox_id)); | |
| break; | |
| } | |
| case EFC_EVT_ABTS_RCVD: | |
| /* sm: / process ABTS */ | |
| efc_log_err(efc, "Unexpected event:%s\n", | |
| efc_sm_event_name(evt)); | |
| break; | |
| case EFC_EVT_NODE_ACTIVE_IO_LIST_EMPTY: | |
| break; | |
| case EFC_EVT_NODE_REFOUND: | |
| break; | |
| case EFC_EVT_NODE_MISSING: | |
| if (node->nport->enable_rscn) | |
| efc_node_transition(node, __efc_d_device_gone, NULL); | |
| break; | |
| case EFC_EVT_SRRS_ELS_CMPL_OK: | |
| /* T, or I+T, PRLI accept completed ok */ | |
| WARN_ON(!node->els_cmpl_cnt); | |
| node->els_cmpl_cnt--; | |
| break; | |
| case EFC_EVT_SRRS_ELS_CMPL_FAIL: | |
| /* T, or I+T, PRLI accept failed to complete */ | |
| WARN_ON(!node->els_cmpl_cnt); | |
| node->els_cmpl_cnt--; | |
| node_printf(node, "Failed to send PRLI LS_ACC\n"); | |
| break; | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_device_gone(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node_cb *cbdata = arg; | |
| struct efc_node *node = ctx->app; | |
| struct efc *efc = node->efc; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_ENTER: { | |
| int rc = EFC_SCSI_CALL_COMPLETE; | |
| int rc_2 = EFC_SCSI_CALL_COMPLETE; | |
| static const char * const labels[] = { | |
| "none", "initiator", "target", "initiator+target" | |
| }; | |
| efc_log_info(efc, "[%s] missing (%s) WWPN %s WWNN %s\n", | |
| node->display_name, | |
| labels[(node->targ << 1) | (node->init)], | |
| node->wwpn, node->wwnn); | |
| switch (efc_node_get_enable(node)) { | |
| case EFC_NODE_ENABLE_T_TO_T: | |
| case EFC_NODE_ENABLE_I_TO_T: | |
| case EFC_NODE_ENABLE_IT_TO_T: | |
| rc = efc->tt.scsi_del_node(efc, node, | |
| EFC_SCSI_TARGET_MISSING); | |
| break; | |
| case EFC_NODE_ENABLE_T_TO_I: | |
| case EFC_NODE_ENABLE_I_TO_I: | |
| case EFC_NODE_ENABLE_IT_TO_I: | |
| rc = efc->tt.scsi_del_node(efc, node, | |
| EFC_SCSI_INITIATOR_MISSING); | |
| break; | |
| case EFC_NODE_ENABLE_T_TO_IT: | |
| rc = efc->tt.scsi_del_node(efc, node, | |
| EFC_SCSI_INITIATOR_MISSING); | |
| break; | |
| case EFC_NODE_ENABLE_I_TO_IT: | |
| rc = efc->tt.scsi_del_node(efc, node, | |
| EFC_SCSI_TARGET_MISSING); | |
| break; | |
| case EFC_NODE_ENABLE_IT_TO_IT: | |
| rc = efc->tt.scsi_del_node(efc, node, | |
| EFC_SCSI_INITIATOR_MISSING); | |
| rc_2 = efc->tt.scsi_del_node(efc, node, | |
| EFC_SCSI_TARGET_MISSING); | |
| break; | |
| default: | |
| rc = EFC_SCSI_CALL_COMPLETE; | |
| break; | |
| } | |
| if (rc == EFC_SCSI_CALL_COMPLETE && | |
| rc_2 == EFC_SCSI_CALL_COMPLETE) | |
| efc_node_post_event(node, EFC_EVT_SHUTDOWN, NULL); | |
| break; | |
| } | |
| case EFC_EVT_NODE_REFOUND: | |
| /* two approaches, reauthenticate with PLOGI/PRLI, or ADISC */ | |
| /* reauthenticate with PLOGI/PRLI */ | |
| /* efc_node_transition(node, __efc_d_discovered, NULL); */ | |
| /* reauthenticate with ADISC */ | |
| /* sm: / send ADISC */ | |
| efc_send_adisc(node); | |
| efc_node_transition(node, __efc_d_wait_adisc_rsp, NULL); | |
| break; | |
| case EFC_EVT_PLOGI_RCVD: { | |
| /* sm: / save sparams, set send_plogi_acc, post implicit | |
| * logout | |
| * Save plogi parameters | |
| */ | |
| efc_node_save_sparms(node, cbdata->payload->dma.virt); | |
| efc_send_ls_acc_after_attach(node, | |
| cbdata->header->dma.virt, | |
| EFC_NODE_SEND_LS_ACC_PLOGI); | |
| /* | |
| * Restart node attach with new service parameters, and send | |
| * ACC | |
| */ | |
| efc_node_post_event(node, EFC_EVT_SHUTDOWN_IMPLICIT_LOGO, | |
| NULL); | |
| break; | |
| } | |
| case EFC_EVT_FCP_CMD_RCVD: { | |
| /* most likely a stale frame (received prior to link down), | |
| * if attempt to send LOGO, will probably timeout and eat | |
| * up 20s; thus, drop FCP_CMND | |
| */ | |
| node_printf(node, "FCP_CMND received, drop\n"); | |
| break; | |
| } | |
| case EFC_EVT_LOGO_RCVD: { | |
| /* I, T, I+T */ | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| node_printf(node, "%s received attached=%d\n", | |
| efc_sm_event_name(evt), node->attached); | |
| /* sm: / send LOGO acc */ | |
| efc_send_logo_acc(node, be16_to_cpu(hdr->fh_ox_id)); | |
| efc_node_transition(node, __efc_d_wait_logo_acc_cmpl, NULL); | |
| break; | |
| } | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } | |
| void | |
| __efc_d_wait_adisc_rsp(struct efc_sm_ctx *ctx, | |
| enum efc_sm_event evt, void *arg) | |
| { | |
| struct efc_node_cb *cbdata = arg; | |
| struct efc_node *node = ctx->app; | |
| efc_node_evt_set(ctx, evt, __func__); | |
| node_sm_trace(); | |
| switch (evt) { | |
| case EFC_EVT_SRRS_ELS_REQ_OK: | |
| if (efc_node_check_els_req(ctx, evt, arg, ELS_ADISC, | |
| __efc_d_common, __func__)) | |
| return; | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| efc_node_transition(node, __efc_d_device_ready, NULL); | |
| break; | |
| case EFC_EVT_SRRS_ELS_REQ_RJT: | |
| /* received an LS_RJT, in this case, send shutdown | |
| * (explicit logo) event which will unregister the node, | |
| * and start over with PLOGI | |
| */ | |
| if (efc_node_check_els_req(ctx, evt, arg, ELS_ADISC, | |
| __efc_d_common, __func__)) | |
| return; | |
| WARN_ON(!node->els_req_cnt); | |
| node->els_req_cnt--; | |
| /* sm: / post explicit logout */ | |
| efc_node_post_event(node, | |
| EFC_EVT_SHUTDOWN_EXPLICIT_LOGO, | |
| NULL); | |
| break; | |
| case EFC_EVT_LOGO_RCVD: { | |
| /* In this case, we have the equivalent of an LS_RJT for | |
| * the ADISC, so we need to abort the ADISC, and re-login | |
| * with PLOGI | |
| */ | |
| /* sm: / request abort, send LOGO acc */ | |
| struct fc_frame_header *hdr = cbdata->header->dma.virt; | |
| node_printf(node, "%s received attached=%d\n", | |
| efc_sm_event_name(evt), node->attached); | |
| efc_send_logo_acc(node, be16_to_cpu(hdr->fh_ox_id)); | |
| efc_node_transition(node, __efc_d_wait_logo_acc_cmpl, NULL); | |
| break; | |
| } | |
| default: | |
| __efc_d_common(__func__, ctx, evt, arg); | |
| } | |
| } |