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