// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2021 Broadcom. All Rights Reserved. The term * “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. */ /* * domain_sm Domain State Machine: States */ #include "efc.h" int efc_domain_cb(void *arg, int event, void *data) { struct efc *efc = arg; struct efc_domain *domain = NULL; int rc = 0; unsigned long flags = 0; if (event != EFC_HW_DOMAIN_FOUND) domain = data; /* Accept domain callback events from the user driver */ spin_lock_irqsave(&efc->lock, flags); switch (event) { case EFC_HW_DOMAIN_FOUND: { u64 fcf_wwn = 0; struct efc_domain_record *drec = data; /* extract the fcf_wwn */ fcf_wwn = be64_to_cpu(*((__be64 *)drec->wwn)); efc_log_debug(efc, "Domain found: wwn %016llX\n", fcf_wwn); /* lookup domain, or allocate a new one */ domain = efc->domain; if (!domain) { domain = efc_domain_alloc(efc, fcf_wwn); if (!domain) { efc_log_err(efc, "efc_domain_alloc() failed\n"); rc = -1; break; } efc_sm_transition(&domain->drvsm, __efc_domain_init, NULL); } efc_domain_post_event(domain, EFC_EVT_DOMAIN_FOUND, drec); break; } case EFC_HW_DOMAIN_LOST: domain_trace(domain, "EFC_HW_DOMAIN_LOST:\n"); efc->hold_frames = true; efc_domain_post_event(domain, EFC_EVT_DOMAIN_LOST, NULL); break; case EFC_HW_DOMAIN_ALLOC_OK: domain_trace(domain, "EFC_HW_DOMAIN_ALLOC_OK:\n"); efc_domain_post_event(domain, EFC_EVT_DOMAIN_ALLOC_OK, NULL); break; case EFC_HW_DOMAIN_ALLOC_FAIL: domain_trace(domain, "EFC_HW_DOMAIN_ALLOC_FAIL:\n"); efc_domain_post_event(domain, EFC_EVT_DOMAIN_ALLOC_FAIL, NULL); break; case EFC_HW_DOMAIN_ATTACH_OK: domain_trace(domain, "EFC_HW_DOMAIN_ATTACH_OK:\n"); efc_domain_post_event(domain, EFC_EVT_DOMAIN_ATTACH_OK, NULL); break; case EFC_HW_DOMAIN_ATTACH_FAIL: domain_trace(domain, "EFC_HW_DOMAIN_ATTACH_FAIL:\n"); efc_domain_post_event(domain, EFC_EVT_DOMAIN_ATTACH_FAIL, NULL); break; case EFC_HW_DOMAIN_FREE_OK: domain_trace(domain, "EFC_HW_DOMAIN_FREE_OK:\n"); efc_domain_post_event(domain, EFC_EVT_DOMAIN_FREE_OK, NULL); break; case EFC_HW_DOMAIN_FREE_FAIL: domain_trace(domain, "EFC_HW_DOMAIN_FREE_FAIL:\n"); efc_domain_post_event(domain, EFC_EVT_DOMAIN_FREE_FAIL, NULL); break; default: efc_log_warn(efc, "unsupported event %#x\n", event); } spin_unlock_irqrestore(&efc->lock, flags); if (efc->domain && domain->req_accept_frames) { domain->req_accept_frames = false; efc->hold_frames = false; } return rc; } static void _efc_domain_free(struct kref *arg) { struct efc_domain *domain = container_of(arg, struct efc_domain, ref); struct efc *efc = domain->efc; if (efc->domain_free_cb) (*efc->domain_free_cb)(efc, efc->domain_free_cb_arg); kfree(domain); } void efc_domain_free(struct efc_domain *domain) { struct efc *efc; efc = domain->efc; /* Hold frames to clear the domain pointer from the xport lookup */ efc->hold_frames = false; efc_log_debug(efc, "Domain free: wwn %016llX\n", domain->fcf_wwn); xa_destroy(&domain->lookup); efc->domain = NULL; kref_put(&domain->ref, domain->release); } struct efc_domain * efc_domain_alloc(struct efc *efc, uint64_t fcf_wwn) { struct efc_domain *domain; domain = kzalloc(sizeof(*domain), GFP_ATOMIC); if (!domain) return NULL; domain->efc = efc; domain->drvsm.app = domain; /* initialize refcount */ kref_init(&domain->ref); domain->release = _efc_domain_free; xa_init(&domain->lookup); INIT_LIST_HEAD(&domain->nport_list); efc->domain = domain; domain->fcf_wwn = fcf_wwn; efc_log_debug(efc, "Domain allocated: wwn %016llX\n", domain->fcf_wwn); return domain; } void efc_register_domain_free_cb(struct efc *efc, void (*callback)(struct efc *efc, void *arg), void *arg) { /* Register a callback to be called when the domain is freed */ efc->domain_free_cb = callback; efc->domain_free_cb_arg = arg; if (!efc->domain && callback) (*callback)(efc, arg); } static void __efc_domain_common(const char *funcname, struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { struct efc_domain *domain = ctx->app; switch (evt) { case EFC_EVT_ENTER: case EFC_EVT_REENTER: case EFC_EVT_EXIT: case EFC_EVT_ALL_CHILD_NODES_FREE: /* * this can arise if an FLOGI fails on the NPORT, * and the NPORT is shutdown */ break; default: efc_log_warn(domain->efc, "%-20s %-20s not handled\n", funcname, efc_sm_event_name(evt)); } } static void __efc_domain_common_shutdown(const char *funcname, struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { struct efc_domain *domain = ctx->app; switch (evt) { case EFC_EVT_ENTER: case EFC_EVT_REENTER: case EFC_EVT_EXIT: break; case EFC_EVT_DOMAIN_FOUND: /* save drec, mark domain_found_pending */ memcpy(&domain->pending_drec, arg, sizeof(domain->pending_drec)); domain->domain_found_pending = true; break; case EFC_EVT_DOMAIN_LOST: /* unmark domain_found_pending */ domain->domain_found_pending = false; break; default: efc_log_warn(domain->efc, "%-20s %-20s not handled\n", funcname, efc_sm_event_name(evt)); } } #define std_domain_state_decl(...)\ struct efc_domain *domain = NULL;\ struct efc *efc = NULL;\ \ WARN_ON(!ctx || !ctx->app);\ domain = ctx->app;\ WARN_ON(!domain->efc);\ efc = domain->efc void __efc_domain_init(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { std_domain_state_decl(); domain_sm_trace(domain); switch (evt) { case EFC_EVT_ENTER: domain->attached = false; break; case EFC_EVT_DOMAIN_FOUND: { u32 i; struct efc_domain_record *drec = arg; struct efc_nport *nport; u64 my_wwnn = efc->req_wwnn; u64 my_wwpn = efc->req_wwpn; __be64 bewwpn; if (my_wwpn == 0 || my_wwnn == 0) { efc_log_debug(efc, "using default hardware WWN config\n"); my_wwpn = efc->def_wwpn; my_wwnn = efc->def_wwnn; } efc_log_debug(efc, "Create nport WWPN %016llX WWNN %016llX\n", my_wwpn, my_wwnn); /* Allocate a nport and transition to __efc_nport_allocated */ nport = efc_nport_alloc(domain, my_wwpn, my_wwnn, U32_MAX, efc->enable_ini, efc->enable_tgt); if (!nport) { efc_log_err(efc, "efc_nport_alloc() failed\n"); break; } efc_sm_transition(&nport->sm, __efc_nport_allocated, NULL); bewwpn = cpu_to_be64(nport->wwpn); /* allocate struct efc_nport object for local port * Note: drec->fc_id is ALPA from read_topology only if loop */ if (efc_cmd_nport_alloc(efc, nport, NULL, (uint8_t *)&bewwpn)) { efc_log_err(efc, "Can't allocate port\n"); efc_nport_free(nport); break; } domain->is_loop = drec->is_loop; /* * If the loop position map includes ALPA == 0, * then we are in a public loop (NL_PORT) * Note that the first element of the loopmap[] * contains the count of elements, and if * ALPA == 0 is present, it will occupy the first * location after the count. */ domain->is_nlport = drec->map.loop[1] == 0x00; if (!domain->is_loop) { /* Initiate HW domain alloc */ if (efc_cmd_domain_alloc(efc, domain, drec->index)) { efc_log_err(efc, "Failed to initiate HW domain allocation\n"); break; } efc_sm_transition(ctx, __efc_domain_wait_alloc, arg); break; } efc_log_debug(efc, "%s fc_id=%#x speed=%d\n", drec->is_loop ? (domain->is_nlport ? "public-loop" : "loop") : "other", drec->fc_id, drec->speed); nport->fc_id = drec->fc_id; nport->topology = EFC_NPORT_TOPO_FC_AL; snprintf(nport->display_name, sizeof(nport->display_name), "s%06x", drec->fc_id); if (efc->enable_ini) { u32 count = drec->map.loop[0]; efc_log_debug(efc, "%d position map entries\n", count); for (i = 1; i <= count; i++) { if (drec->map.loop[i] != drec->fc_id) { struct efc_node *node; efc_log_debug(efc, "%#x -> %#x\n", drec->fc_id, drec->map.loop[i]); node = efc_node_alloc(nport, drec->map.loop[i], false, true); if (!node) { efc_log_err(efc, "efc_node_alloc() failed\n"); break; } efc_node_transition(node, __efc_d_wait_loop, NULL); } } } /* Initiate HW domain alloc */ if (efc_cmd_domain_alloc(efc, domain, drec->index)) { efc_log_err(efc, "Failed to initiate HW domain allocation\n"); break; } efc_sm_transition(ctx, __efc_domain_wait_alloc, arg); break; } default: __efc_domain_common(__func__, ctx, evt, arg); } } void __efc_domain_wait_alloc(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { std_domain_state_decl(); domain_sm_trace(domain); switch (evt) { case EFC_EVT_DOMAIN_ALLOC_OK: { struct fc_els_flogi *sp; struct efc_nport *nport; nport = domain->nport; if (WARN_ON(!nport)) return; sp = (struct fc_els_flogi *)nport->service_params; /* Save the domain service parameters */ memcpy(domain->service_params + 4, domain->dma.virt, sizeof(struct fc_els_flogi) - 4); memcpy(nport->service_params + 4, domain->dma.virt, sizeof(struct fc_els_flogi) - 4); /* * Update the nport's service parameters, * user might have specified non-default names */ sp->fl_wwpn = cpu_to_be64(nport->wwpn); sp->fl_wwnn = cpu_to_be64(nport->wwnn); /* * Take the loop topology path, * unless we are an NL_PORT (public loop) */ if (domain->is_loop && !domain->is_nlport) { /* * For loop, we already have our FC ID * and don't need fabric login. * Transition to the allocated state and * post an event to attach to * the domain. Note that this breaks the * normal action/transition * pattern here to avoid a race with the * domain attach callback. */ /* sm: is_loop / domain_attach */ efc_sm_transition(ctx, __efc_domain_allocated, NULL); __efc_domain_attach_internal(domain, nport->fc_id); break; } { struct efc_node *node; /* alloc fabric node, send FLOGI */ node = efc_node_find(nport, FC_FID_FLOGI); if (node) { efc_log_err(efc, "Fabric Controller node already exists\n"); break; } node = efc_node_alloc(nport, FC_FID_FLOGI, false, false); if (!node) { efc_log_err(efc, "Error: efc_node_alloc() failed\n"); } else { efc_node_transition(node, __efc_fabric_init, NULL); } /* Accept frames */ domain->req_accept_frames = true; } /* sm: / start fabric logins */ efc_sm_transition(ctx, __efc_domain_allocated, NULL); break; } case EFC_EVT_DOMAIN_ALLOC_FAIL: efc_log_err(efc, "%s recv'd waiting for DOMAIN_ALLOC_OK;", efc_sm_event_name(evt)); efc_log_err(efc, "shutting down domain\n"); domain->req_domain_free = true; break; case EFC_EVT_DOMAIN_FOUND: /* Should not happen */ break; case EFC_EVT_DOMAIN_LOST: efc_log_debug(efc, "%s received while waiting for hw_domain_alloc()\n", efc_sm_event_name(evt)); efc_sm_transition(ctx, __efc_domain_wait_domain_lost, NULL); break; default: __efc_domain_common(__func__, ctx, evt, arg); } } void __efc_domain_allocated(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { std_domain_state_decl(); domain_sm_trace(domain); switch (evt) { case EFC_EVT_DOMAIN_REQ_ATTACH: { int rc = 0; u32 fc_id; if (WARN_ON(!arg)) return; fc_id = *((u32 *)arg); efc_log_debug(efc, "Requesting hw domain attach fc_id x%x\n", fc_id); /* Update nport lookup */ rc = xa_err(xa_store(&domain->lookup, fc_id, domain->nport, GFP_ATOMIC)); if (rc) { efc_log_err(efc, "Sport lookup store failed: %d\n", rc); return; } /* Update display name for the nport */ efc_node_fcid_display(fc_id, domain->nport->display_name, sizeof(domain->nport->display_name)); /* Issue domain attach call */ rc = efc_cmd_domain_attach(efc, domain, fc_id); if (rc) { efc_log_err(efc, "efc_hw_domain_attach failed: %d\n", rc); return; } /* sm: / domain_attach */ efc_sm_transition(ctx, __efc_domain_wait_attach, NULL); break; } case EFC_EVT_DOMAIN_FOUND: /* Should not happen */ efc_log_err(efc, "%s: evt: %d should not happen\n", __func__, evt); break; case EFC_EVT_DOMAIN_LOST: { efc_log_debug(efc, "%s received while in EFC_EVT_DOMAIN_REQ_ATTACH\n", efc_sm_event_name(evt)); if (!list_empty(&domain->nport_list)) { /* * if there are nports, transition to * wait state and send shutdown to each * nport */ struct efc_nport *nport = NULL, *nport_next = NULL; efc_sm_transition(ctx, __efc_domain_wait_nports_free, NULL); list_for_each_entry_safe(nport, nport_next, &domain->nport_list, list_entry) { efc_sm_post_event(&nport->sm, EFC_EVT_SHUTDOWN, NULL); } } else { /* no nports exist, free domain */ efc_sm_transition(ctx, __efc_domain_wait_shutdown, NULL); if (efc_cmd_domain_free(efc, domain)) efc_log_err(efc, "hw_domain_free failed\n"); } break; } default: __efc_domain_common(__func__, ctx, evt, arg); } } void __efc_domain_wait_attach(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { std_domain_state_decl(); domain_sm_trace(domain); switch (evt) { case EFC_EVT_DOMAIN_ATTACH_OK: { struct efc_node *node = NULL; struct efc_nport *nport, *next_nport; unsigned long index; /* * Set domain notify pending state to avoid * duplicate domain event post */ domain->domain_notify_pend = true; /* Mark as attached */ domain->attached = true; /* Transition to ready */ /* sm: / forward event to all nports and nodes */ efc_sm_transition(ctx, __efc_domain_ready, NULL); /* We have an FCFI, so we can accept frames */ domain->req_accept_frames = true; /* * Notify all nodes that the domain attach request * has completed * Note: nport will have already received notification * of nport attached as a result of the HW's port attach. */ list_for_each_entry_safe(nport, next_nport, &domain->nport_list, list_entry) { xa_for_each(&nport->lookup, index, node) { efc_node_post_event(node, EFC_EVT_DOMAIN_ATTACH_OK, NULL); } } domain->domain_notify_pend = false; break; } case EFC_EVT_DOMAIN_ATTACH_FAIL: efc_log_debug(efc, "%s received while waiting for hw attach\n", efc_sm_event_name(evt)); break; case EFC_EVT_DOMAIN_FOUND: /* Should not happen */ efc_log_err(efc, "%s: evt: %d should not happen\n", __func__, evt); break; case EFC_EVT_DOMAIN_LOST: /* * Domain lost while waiting for an attach to complete, * go to a state that waits for the domain attach to * complete, then handle domain lost */ efc_sm_transition(ctx, __efc_domain_wait_domain_lost, NULL); break; case EFC_EVT_DOMAIN_REQ_ATTACH: /* * In P2P we can get an attach request from * the other FLOGI path, so drop this one */ break; default: __efc_domain_common(__func__, ctx, evt, arg); } } void __efc_domain_ready(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { std_domain_state_decl(); domain_sm_trace(domain); switch (evt) { case EFC_EVT_ENTER: { /* start any pending vports */ if (efc_vport_start(domain)) { efc_log_debug(domain->efc, "efc_vport_start didn't start vports\n"); } break; } case EFC_EVT_DOMAIN_LOST: { if (!list_empty(&domain->nport_list)) { /* * if there are nports, transition to wait state * and send shutdown to each nport */ struct efc_nport *nport = NULL, *nport_next = NULL; efc_sm_transition(ctx, __efc_domain_wait_nports_free, NULL); list_for_each_entry_safe(nport, nport_next, &domain->nport_list, list_entry) { efc_sm_post_event(&nport->sm, EFC_EVT_SHUTDOWN, NULL); } } else { /* no nports exist, free domain */ efc_sm_transition(ctx, __efc_domain_wait_shutdown, NULL); if (efc_cmd_domain_free(efc, domain)) efc_log_err(efc, "hw_domain_free failed\n"); } break; } case EFC_EVT_DOMAIN_FOUND: /* Should not happen */ efc_log_err(efc, "%s: evt: %d should not happen\n", __func__, evt); break; case EFC_EVT_DOMAIN_REQ_ATTACH: { /* can happen during p2p */ u32 fc_id; fc_id = *((u32 *)arg); /* Assume that the domain is attached */ WARN_ON(!domain->attached); /* * Verify that the requested FC_ID * is the same as the one we're working with */ WARN_ON(domain->nport->fc_id != fc_id); break; } default: __efc_domain_common(__func__, ctx, evt, arg); } } void __efc_domain_wait_nports_free(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { std_domain_state_decl(); domain_sm_trace(domain); /* Wait for nodes to free prior to the domain shutdown */ switch (evt) { case EFC_EVT_ALL_CHILD_NODES_FREE: { int rc; /* sm: / efc_hw_domain_free */ efc_sm_transition(ctx, __efc_domain_wait_shutdown, NULL); /* Request efc_hw_domain_free and wait for completion */ rc = efc_cmd_domain_free(efc, domain); if (rc) { efc_log_err(efc, "efc_hw_domain_free() failed: %d\n", rc); } break; } default: __efc_domain_common_shutdown(__func__, ctx, evt, arg); } } void __efc_domain_wait_shutdown(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { std_domain_state_decl(); domain_sm_trace(domain); switch (evt) { case EFC_EVT_DOMAIN_FREE_OK: /* sm: / domain_free */ if (domain->domain_found_pending) { /* * save fcf_wwn and drec from this domain, * free current domain and allocate * a new one with the same fcf_wwn * could use a SLI-4 "re-register VPI" * operation here? */ u64 fcf_wwn = domain->fcf_wwn; struct efc_domain_record drec = domain->pending_drec; efc_log_debug(efc, "Reallocating domain\n"); domain->req_domain_free = true; domain = efc_domain_alloc(efc, fcf_wwn); if (!domain) { efc_log_err(efc, "efc_domain_alloc() failed\n"); return; } /* * got a new domain; at this point, * there are at least two domains * once the req_domain_free flag is processed, * the associated domain will be removed. */ efc_sm_transition(&domain->drvsm, __efc_domain_init, NULL); efc_sm_post_event(&domain->drvsm, EFC_EVT_DOMAIN_FOUND, &drec); } else { domain->req_domain_free = true; } break; default: __efc_domain_common_shutdown(__func__, ctx, evt, arg); } } void __efc_domain_wait_domain_lost(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg) { std_domain_state_decl(); domain_sm_trace(domain); /* * Wait for the domain alloc/attach completion * after receiving a domain lost. */ switch (evt) { case EFC_EVT_DOMAIN_ALLOC_OK: case EFC_EVT_DOMAIN_ATTACH_OK: { if (!list_empty(&domain->nport_list)) { /* * if there are nports, transition to * wait state and send shutdown to each nport */ struct efc_nport *nport = NULL, *nport_next = NULL; efc_sm_transition(ctx, __efc_domain_wait_nports_free, NULL); list_for_each_entry_safe(nport, nport_next, &domain->nport_list, list_entry) { efc_sm_post_event(&nport->sm, EFC_EVT_SHUTDOWN, NULL); } } else { /* no nports exist, free domain */ efc_sm_transition(ctx, __efc_domain_wait_shutdown, NULL); if (efc_cmd_domain_free(efc, domain)) efc_log_err(efc, "hw_domain_free() failed\n"); } break; } case EFC_EVT_DOMAIN_ALLOC_FAIL: case EFC_EVT_DOMAIN_ATTACH_FAIL: efc_log_err(efc, "[domain] %-20s: failed\n", efc_sm_event_name(evt)); break; default: __efc_domain_common_shutdown(__func__, ctx, evt, arg); } } void __efc_domain_attach_internal(struct efc_domain *domain, u32 s_id) { memcpy(domain->dma.virt, ((uint8_t *)domain->flogi_service_params) + 4, sizeof(struct fc_els_flogi) - 4); (void)efc_sm_post_event(&domain->drvsm, EFC_EVT_DOMAIN_REQ_ATTACH, &s_id); } void efc_domain_attach(struct efc_domain *domain, u32 s_id) { __efc_domain_attach_internal(domain, s_id); } int efc_domain_post_event(struct efc_domain *domain, enum efc_sm_event event, void *arg) { int rc; bool req_domain_free; rc = efc_sm_post_event(&domain->drvsm, event, arg); req_domain_free = domain->req_domain_free; domain->req_domain_free = false; if (req_domain_free) efc_domain_free(domain); return rc; } static void efct_domain_process_pending(struct efc_domain *domain) { struct efc *efc = domain->efc; struct efc_hw_sequence *seq = NULL; u32 processed = 0; unsigned long flags = 0; for (;;) { /* need to check for hold frames condition after each frame * processed because any given frame could cause a transition * to a state that holds frames */ if (efc->hold_frames) break; /* Get next frame/sequence */ spin_lock_irqsave(&efc->pend_frames_lock, flags); if (!list_empty(&efc->pend_frames)) { seq = list_first_entry(&efc->pend_frames, struct efc_hw_sequence, list_entry); list_del(&seq->list_entry); } if (!seq) { processed = efc->pend_frames_processed; efc->pend_frames_processed = 0; spin_unlock_irqrestore(&efc->pend_frames_lock, flags); break; } efc->pend_frames_processed++; spin_unlock_irqrestore(&efc->pend_frames_lock, flags); /* now dispatch frame(s) to dispatch function */ if (efc_domain_dispatch_frame(domain, seq)) efc->tt.hw_seq_free(efc, seq); seq = NULL; } if (processed != 0) efc_log_debug(efc, "%u domain frames held and processed\n", processed); } void efc_dispatch_frame(struct efc *efc, struct efc_hw_sequence *seq) { struct efc_domain *domain = efc->domain; /* * If we are holding frames or the domain is not yet registered or * there's already frames on the pending list, * then add the new frame to pending list */ if (!domain || efc->hold_frames || !list_empty(&efc->pend_frames)) { unsigned long flags = 0; spin_lock_irqsave(&efc->pend_frames_lock, flags); INIT_LIST_HEAD(&seq->list_entry); list_add_tail(&seq->list_entry, &efc->pend_frames); spin_unlock_irqrestore(&efc->pend_frames_lock, flags); if (domain) { /* immediately process pending frames */ efct_domain_process_pending(domain); } } else { /* * We are not holding frames and pending list is empty, * just process frame. A non-zero return means the frame * was not handled - so cleanup */ if (efc_domain_dispatch_frame(domain, seq)) efc->tt.hw_seq_free(efc, seq); } } int efc_domain_dispatch_frame(void *arg, struct efc_hw_sequence *seq) { struct efc_domain *domain = (struct efc_domain *)arg; struct efc *efc = domain->efc; struct fc_frame_header *hdr; struct efc_node *node = NULL; struct efc_nport *nport = NULL; unsigned long flags = 0; u32 s_id, d_id, rc = EFC_HW_SEQ_FREE; if (!seq->header || !seq->header->dma.virt || !seq->payload->dma.virt) { efc_log_err(efc, "Sequence header or payload is null\n"); return rc; } hdr = seq->header->dma.virt; /* extract the s_id and d_id */ s_id = ntoh24(hdr->fh_s_id); d_id = ntoh24(hdr->fh_d_id); spin_lock_irqsave(&efc->lock, flags); nport = efc_nport_find(domain, d_id); if (!nport) { if (hdr->fh_type == FC_TYPE_FCP) { /* Drop frame */ efc_log_warn(efc, "FCP frame with invalid d_id x%x\n", d_id); goto out; } /* p2p will use this case */ nport = domain->nport; if (!nport || !kref_get_unless_zero(&nport->ref)) { efc_log_err(efc, "Physical nport is NULL\n"); goto out; } } /* Lookup the node given the remote s_id */ node = efc_node_find(nport, s_id); /* If not found, then create a new node */ if (!node) { /* * If this is solicited data or control based on R_CTL and * there is no node context, then we can drop the frame */ if ((hdr->fh_r_ctl == FC_RCTL_DD_SOL_DATA) || (hdr->fh_r_ctl == FC_RCTL_DD_SOL_CTL)) { efc_log_debug(efc, "sol data/ctrl frame without node\n"); goto out_release; } node = efc_node_alloc(nport, s_id, false, false); if (!node) { efc_log_err(efc, "efc_node_alloc() failed\n"); goto out_release; } /* don't send PLOGI on efc_d_init entry */ efc_node_init_device(node, false); } if (node->hold_frames || !list_empty(&node->pend_frames)) { /* add frame to node's pending list */ spin_lock(&node->pend_frames_lock); INIT_LIST_HEAD(&seq->list_entry); list_add_tail(&seq->list_entry, &node->pend_frames); spin_unlock(&node->pend_frames_lock); rc = EFC_HW_SEQ_HOLD; goto out_release; } /* now dispatch frame to the node frame handler */ efc_node_dispatch_frame(node, seq); out_release: kref_put(&nport->ref, nport->release); out: spin_unlock_irqrestore(&efc->lock, flags); return rc; } void efc_node_dispatch_frame(void *arg, struct efc_hw_sequence *seq) { struct fc_frame_header *hdr = seq->header->dma.virt; u32 port_id; struct efc_node *node = (struct efc_node *)arg; struct efc *efc = node->efc; port_id = ntoh24(hdr->fh_s_id); if (WARN_ON(port_id != node->rnode.fc_id)) return; if ((!(ntoh24(hdr->fh_f_ctl) & FC_FC_END_SEQ)) || !(ntoh24(hdr->fh_f_ctl) & FC_FC_SEQ_INIT)) { node_printf(node, "Drop frame hdr = %08x %08x %08x %08x %08x %08x\n", cpu_to_be32(((u32 *)hdr)[0]), cpu_to_be32(((u32 *)hdr)[1]), cpu_to_be32(((u32 *)hdr)[2]), cpu_to_be32(((u32 *)hdr)[3]), cpu_to_be32(((u32 *)hdr)[4]), cpu_to_be32(((u32 *)hdr)[5])); return; } switch (hdr->fh_r_ctl) { case FC_RCTL_ELS_REQ: case FC_RCTL_ELS_REP: efc_node_recv_els_frame(node, seq); break; case FC_RCTL_BA_ABTS: case FC_RCTL_BA_ACC: case FC_RCTL_BA_RJT: case FC_RCTL_BA_NOP: efc_log_err(efc, "Received ABTS:\n"); break; case FC_RCTL_DD_UNSOL_CMD: case FC_RCTL_DD_UNSOL_CTL: switch (hdr->fh_type) { case FC_TYPE_FCP: if ((hdr->fh_r_ctl & 0xf) == FC_RCTL_DD_UNSOL_CMD) { if (!node->fcp_enabled) { efc_node_recv_fcp_cmd(node, seq); break; } efc_log_err(efc, "Recvd FCP CMD. Drop IO\n"); } else if ((hdr->fh_r_ctl & 0xf) == FC_RCTL_DD_SOL_DATA) { node_printf(node, "solicited data recvd. Drop IO\n"); } break; case FC_TYPE_CT: efc_node_recv_ct_frame(node, seq); break; default: break; } break; default: efc_log_err(efc, "Unhandled frame rctl: %02x\n", hdr->fh_r_ctl); } }