root/drivers/scsi/elx/libefc/efc_domain.c
// 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);
        }
}