root/drivers/scsi/elx/libefc/efc_node.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.
 */

#include "efc.h"

int
efc_remote_node_cb(void *arg, int event, void *data)
{
        struct efc *efc = arg;
        struct efc_remote_node *rnode = data;
        struct efc_node *node = rnode->node;
        unsigned long flags = 0;

        spin_lock_irqsave(&efc->lock, flags);
        efc_node_post_event(node, event, NULL);
        spin_unlock_irqrestore(&efc->lock, flags);

        return 0;
}

struct efc_node *
efc_node_find(struct efc_nport *nport, u32 port_id)
{
        /* Find an FC node structure given the FC port ID */
        return xa_load(&nport->lookup, port_id);
}

static void
_efc_node_free(struct kref *arg)
{
        struct efc_node *node = container_of(arg, struct efc_node, ref);
        struct efc *efc = node->efc;
        struct efc_dma *dma;

        dma = &node->sparm_dma_buf;
        dma_pool_free(efc->node_dma_pool, dma->virt, dma->phys);
        memset(dma, 0, sizeof(struct efc_dma));
        mempool_free(node, efc->node_pool);
}

struct efc_node *efc_node_alloc(struct efc_nport *nport,
                                u32 port_id, bool init, bool targ)
{
        int rc;
        struct efc_node *node = NULL;
        struct efc *efc = nport->efc;
        struct efc_dma *dma;

        if (nport->shutting_down) {
                efc_log_debug(efc, "node allocation when shutting down %06x",
                              port_id);
                return NULL;
        }

        node = mempool_alloc(efc->node_pool, GFP_ATOMIC);
        if (!node) {
                efc_log_err(efc, "node allocation failed %06x", port_id);
                return NULL;
        }
        memset(node, 0, sizeof(*node));

        dma = &node->sparm_dma_buf;
        dma->size = NODE_SPARAMS_SIZE;
        dma->virt = dma_pool_zalloc(efc->node_dma_pool, GFP_ATOMIC, &dma->phys);
        if (!dma->virt) {
                efc_log_err(efc, "node dma alloc failed\n");
                goto dma_fail;
        }
        node->rnode.indicator = U32_MAX;
        node->nport = nport;

        node->efc = efc;
        node->init = init;
        node->targ = targ;

        spin_lock_init(&node->pend_frames_lock);
        INIT_LIST_HEAD(&node->pend_frames);
        spin_lock_init(&node->els_ios_lock);
        INIT_LIST_HEAD(&node->els_ios_list);
        node->els_io_enabled = true;

        rc = efc_cmd_node_alloc(efc, &node->rnode, port_id, nport);
        if (rc) {
                efc_log_err(efc, "efc_hw_node_alloc failed: %d\n", rc);
                goto hw_alloc_fail;
        }

        node->rnode.node = node;
        node->sm.app = node;
        node->evtdepth = 0;

        efc_node_update_display_name(node);

        rc = xa_err(xa_store(&nport->lookup, port_id, node, GFP_ATOMIC));
        if (rc) {
                efc_log_err(efc, "Node lookup store failed: %d\n", rc);
                goto xa_fail;
        }

        /* initialize refcount */
        kref_init(&node->ref);
        node->release = _efc_node_free;
        kref_get(&nport->ref);

        return node;

xa_fail:
        efc_node_free_resources(efc, &node->rnode);
hw_alloc_fail:
        dma_pool_free(efc->node_dma_pool, dma->virt, dma->phys);
dma_fail:
        mempool_free(node, efc->node_pool);
        return NULL;
}

void
efc_node_free(struct efc_node *node)
{
        struct efc_nport *nport;
        struct efc *efc;
        int rc = 0;
        struct efc_node *ns = NULL;

        nport = node->nport;
        efc = node->efc;

        node_printf(node, "Free'd\n");

        if (node->refound) {
                /*
                 * Save the name server node. We will send fake RSCN event at
                 * the end to handle ignored RSCN event during node deletion
                 */
                ns = efc_node_find(node->nport, FC_FID_DIR_SERV);
        }

        if (!node->nport) {
                efc_log_err(efc, "Node already Freed\n");
                return;
        }

        /* Free HW resources */
        rc = efc_node_free_resources(efc, &node->rnode);
        if (rc < 0)
                efc_log_err(efc, "efc_hw_node_free failed: %d\n", rc);

        /* if the gidpt_delay_timer is still running, then delete it */
        if (timer_pending(&node->gidpt_delay_timer))
                timer_delete(&node->gidpt_delay_timer);

        xa_erase(&nport->lookup, node->rnode.fc_id);

        /*
         * If the node_list is empty,
         * then post a ALL_CHILD_NODES_FREE event to the nport,
         * after the lock is released.
         * The nport may be free'd as a result of the event.
         */
        if (xa_empty(&nport->lookup))
                efc_sm_post_event(&nport->sm, EFC_EVT_ALL_CHILD_NODES_FREE,
                                  NULL);

        node->nport = NULL;
        node->sm.current_state = NULL;

        kref_put(&nport->ref, nport->release);
        kref_put(&node->ref, node->release);

        if (ns) {
                /* sending fake RSCN event to name server node */
                efc_node_post_event(ns, EFC_EVT_RSCN_RCVD, NULL);
        }
}

static void
efc_dma_copy_in(struct efc_dma *dma, void *buffer, u32 buffer_length)
{
        if (!dma || !buffer || !buffer_length)
                return;

        if (buffer_length > dma->size)
                buffer_length = dma->size;

        memcpy(dma->virt, buffer, buffer_length);
        dma->len = buffer_length;
}

int
efc_node_attach(struct efc_node *node)
{
        int rc = 0;
        struct efc_nport *nport = node->nport;
        struct efc_domain *domain = nport->domain;
        struct efc *efc = node->efc;

        if (!domain->attached) {
                efc_log_err(efc, "Warning: unattached domain\n");
                return -EIO;
        }
        /* Update node->wwpn/wwnn */

        efc_node_build_eui_name(node->wwpn, sizeof(node->wwpn),
                                efc_node_get_wwpn(node));
        efc_node_build_eui_name(node->wwnn, sizeof(node->wwnn),
                                efc_node_get_wwnn(node));

        efc_dma_copy_in(&node->sparm_dma_buf, node->service_params + 4,
                        sizeof(node->service_params) - 4);

        /* take lock to protect node->rnode.attached */
        rc = efc_cmd_node_attach(efc, &node->rnode, &node->sparm_dma_buf);
        if (rc < 0)
                efc_log_debug(efc, "efc_hw_node_attach failed: %d\n", rc);

        return rc;
}

void
efc_node_fcid_display(u32 fc_id, char *buffer, u32 buffer_length)
{
        switch (fc_id) {
        case FC_FID_FLOGI:
                snprintf(buffer, buffer_length, "fabric");
                break;
        case FC_FID_FCTRL:
                snprintf(buffer, buffer_length, "fabctl");
                break;
        case FC_FID_DIR_SERV:
                snprintf(buffer, buffer_length, "nserve");
                break;
        default:
                if (fc_id == FC_FID_DOM_MGR) {
                        snprintf(buffer, buffer_length, "dctl%02x",
                                 (fc_id & 0x0000ff));
                } else {
                        snprintf(buffer, buffer_length, "%06x", fc_id);
                }
                break;
        }
}

void
efc_node_update_display_name(struct efc_node *node)
{
        u32 port_id = node->rnode.fc_id;
        struct efc_nport *nport = node->nport;
        char portid_display[16];

        efc_node_fcid_display(port_id, portid_display, sizeof(portid_display));

        snprintf(node->display_name, sizeof(node->display_name), "%s.%s",
                 nport->display_name, portid_display);
}

void
efc_node_send_ls_io_cleanup(struct efc_node *node)
{
        if (node->send_ls_acc != EFC_NODE_SEND_LS_ACC_NONE) {
                efc_log_debug(node->efc, "[%s] cleaning up LS_ACC oxid=0x%x\n",
                              node->display_name, node->ls_acc_oxid);

                node->send_ls_acc = EFC_NODE_SEND_LS_ACC_NONE;
                node->ls_acc_io = NULL;
        }
}

static void efc_node_handle_implicit_logo(struct efc_node *node)
{
        int rc;

        /*
         * currently, only case for implicit logo is PLOGI
         * recvd. Thus, node's ELS IO pending list won't be
         * empty (PLOGI will be on it)
         */
        WARN_ON(node->send_ls_acc != EFC_NODE_SEND_LS_ACC_PLOGI);
        node_printf(node, "Reason: implicit logout, re-authenticate\n");

        /* Re-attach node with the same HW node resources */
        node->req_free = false;
        rc = efc_node_attach(node);
        efc_node_transition(node, __efc_d_wait_node_attach, NULL);
        node->els_io_enabled = true;

        if (rc < 0)
                efc_node_post_event(node, EFC_EVT_NODE_ATTACH_FAIL, NULL);
}

static void efc_node_handle_explicit_logo(struct efc_node *node)
{
        s8 pend_frames_empty;
        unsigned long flags = 0;

        /* cleanup any pending LS_ACC ELSs */
        efc_node_send_ls_io_cleanup(node);

        spin_lock_irqsave(&node->pend_frames_lock, flags);
        pend_frames_empty = list_empty(&node->pend_frames);
        spin_unlock_irqrestore(&node->pend_frames_lock, flags);

        /*
         * there are two scenarios where we want to keep
         * this node alive:
         * 1. there are pending frames that need to be
         *    processed or
         * 2. we're an initiator and the remote node is
         *    a target and we need to re-authenticate
         */
        node_printf(node, "Shutdown: explicit logo pend=%d ", !pend_frames_empty);
        node_printf(node, "nport.ini=%d node.tgt=%d\n",
                    node->nport->enable_ini, node->targ);
        if (!pend_frames_empty || (node->nport->enable_ini && node->targ)) {
                u8 send_plogi = false;

                if (node->nport->enable_ini && node->targ) {
                        /*
                         * we're an initiator and
                         * node shutting down is a target;
                         * we'll need to re-authenticate in
                         * initial state
                         */
                        send_plogi = true;
                }

                /*
                 * transition to __efc_d_init
                 * (will retain HW node resources)
                 */
                node->els_io_enabled = true;
                node->req_free = false;

                /*
                 * either pending frames exist or we are re-authenticating
                 * with PLOGI (or both); in either case, return to initial
                 * state
                 */
                efc_node_init_device(node, send_plogi);
        }
        /* else: let node shutdown occur */
}

static void
efc_node_purge_pending(struct efc_node *node)
{
        struct efc *efc = node->efc;
        struct efc_hw_sequence *frame, *next;
        unsigned long flags = 0;

        spin_lock_irqsave(&node->pend_frames_lock, flags);

        list_for_each_entry_safe(frame, next, &node->pend_frames, list_entry) {
                list_del(&frame->list_entry);
                efc->tt.hw_seq_free(efc, frame);
        }

        spin_unlock_irqrestore(&node->pend_frames_lock, flags);
}

void
__efc_node_shutdown(struct efc_sm_ctx *ctx,
                    enum efc_sm_event evt, void *arg)
{
        struct efc_node *node = ctx->app;

        efc_node_evt_set(ctx, evt, __func__);

        node_sm_trace();

        switch (evt) {
        case EFC_EVT_ENTER: {
                efc_node_hold_frames(node);
                WARN_ON(!efc_els_io_list_empty(node, &node->els_ios_list));
                /* by default, we will be freeing node after we unwind */
                node->req_free = true;

                switch (node->shutdown_reason) {
                case EFC_NODE_SHUTDOWN_IMPLICIT_LOGO:
                        /* Node shutdown b/c of PLOGI received when node
                         * already logged in. We have PLOGI service
                         * parameters, so submit node attach; we won't be
                         * freeing this node
                         */

                        efc_node_handle_implicit_logo(node);
                        break;

                case EFC_NODE_SHUTDOWN_EXPLICIT_LOGO:
                        efc_node_handle_explicit_logo(node);
                        break;

                case EFC_NODE_SHUTDOWN_DEFAULT:
                default: {
                        /*
                         * shutdown due to link down,
                         * node going away (xport event) or
                         * nport shutdown, purge pending and
                         * proceed to cleanup node
                         */

                        /* cleanup any pending LS_ACC ELSs */
                        efc_node_send_ls_io_cleanup(node);

                        node_printf(node,
                                    "Shutdown reason: default, purge pending\n");
                        efc_node_purge_pending(node);
                        break;
                }
                }

                break;
        }
        case EFC_EVT_EXIT:
                efc_node_accept_frames(node);
                break;

        default:
                __efc_node_common(__func__, ctx, evt, arg);
        }
}

static bool
efc_node_check_els_quiesced(struct efc_node *node)
{
        /* check to see if ELS requests, completions are quiesced */
        if (node->els_req_cnt == 0 && node->els_cmpl_cnt == 0 &&
            efc_els_io_list_empty(node, &node->els_ios_list)) {
                if (!node->attached) {
                        /* hw node detach already completed, proceed */
                        node_printf(node, "HW node not attached\n");
                        efc_node_transition(node,
                                            __efc_node_wait_ios_shutdown,
                                             NULL);
                } else {
                        /*
                         * hw node detach hasn't completed,
                         * transition and wait
                         */
                        node_printf(node, "HW node still attached\n");
                        efc_node_transition(node, __efc_node_wait_node_free,
                                            NULL);
                }
                return true;
        }
        return false;
}

void
efc_node_initiate_cleanup(struct efc_node *node)
{
        /*
         * if ELS's have already been quiesced, will move to next state
         * if ELS's have not been quiesced, abort them
         */
        if (!efc_node_check_els_quiesced(node)) {
                efc_node_hold_frames(node);
                efc_node_transition(node, __efc_node_wait_els_shutdown, NULL);
        }
}

void
__efc_node_wait_els_shutdown(struct efc_sm_ctx *ctx,
                             enum efc_sm_event evt, void *arg)
{
        bool check_quiesce = false;
        struct efc_node *node = ctx->app;

        efc_node_evt_set(ctx, evt, __func__);

        node_sm_trace();
        /* Node state machine: Wait for all ELSs to complete */
        switch (evt) {
        case EFC_EVT_ENTER:
                efc_node_hold_frames(node);
                if (efc_els_io_list_empty(node, &node->els_ios_list)) {
                        node_printf(node, "All ELS IOs complete\n");
                        check_quiesce = true;
                }
                break;
        case EFC_EVT_EXIT:
                efc_node_accept_frames(node);
                break;

        case EFC_EVT_SRRS_ELS_REQ_OK:
        case EFC_EVT_SRRS_ELS_REQ_FAIL:
        case EFC_EVT_SRRS_ELS_REQ_RJT:
        case EFC_EVT_ELS_REQ_ABORTED:
                if (WARN_ON(!node->els_req_cnt))
                        break;
                node->els_req_cnt--;
                check_quiesce = true;
                break;

        case EFC_EVT_SRRS_ELS_CMPL_OK:
        case EFC_EVT_SRRS_ELS_CMPL_FAIL:
                if (WARN_ON(!node->els_cmpl_cnt))
                        break;
                node->els_cmpl_cnt--;
                check_quiesce = true;
                break;

        case EFC_EVT_ALL_CHILD_NODES_FREE:
                /* all ELS IO's complete */
                node_printf(node, "All ELS IOs complete\n");
                WARN_ON(!efc_els_io_list_empty(node, &node->els_ios_list));
                check_quiesce = true;
                break;

        case EFC_EVT_NODE_ACTIVE_IO_LIST_EMPTY:
                check_quiesce = true;
                break;

        case EFC_EVT_DOMAIN_ATTACH_OK:
                /* don't care about domain_attach_ok */
                break;

        /* ignore shutdown events as we're already in shutdown path */
        case EFC_EVT_SHUTDOWN:
                /* have default shutdown event take precedence */
                node->shutdown_reason = EFC_NODE_SHUTDOWN_DEFAULT;
                fallthrough;

        case EFC_EVT_SHUTDOWN_EXPLICIT_LOGO:
        case EFC_EVT_SHUTDOWN_IMPLICIT_LOGO:
                node_printf(node, "%s received\n", efc_sm_event_name(evt));
                break;

        default:
                __efc_node_common(__func__, ctx, evt, arg);
        }

        if (check_quiesce)
                efc_node_check_els_quiesced(node);
}

void
__efc_node_wait_node_free(struct efc_sm_ctx *ctx,
                          enum efc_sm_event evt, void *arg)
{
        struct efc_node *node = ctx->app;

        efc_node_evt_set(ctx, evt, __func__);

        node_sm_trace();

        switch (evt) {
        case EFC_EVT_ENTER:
                efc_node_hold_frames(node);
                break;

        case EFC_EVT_EXIT:
                efc_node_accept_frames(node);
                break;

        case EFC_EVT_NODE_FREE_OK:
                /* node is officially no longer attached */
                node->attached = false;
                efc_node_transition(node, __efc_node_wait_ios_shutdown, NULL);
                break;

        case EFC_EVT_ALL_CHILD_NODES_FREE:
        case EFC_EVT_NODE_ACTIVE_IO_LIST_EMPTY:
                /* As IOs and ELS IO's complete we expect to get these events */
                break;

        case EFC_EVT_DOMAIN_ATTACH_OK:
                /* don't care about domain_attach_ok */
                break;

        /* ignore shutdown events as we're already in shutdown path */
        case EFC_EVT_SHUTDOWN:
                /* have default shutdown event take precedence */
                node->shutdown_reason = EFC_NODE_SHUTDOWN_DEFAULT;
                fallthrough;

        case EFC_EVT_SHUTDOWN_EXPLICIT_LOGO:
        case EFC_EVT_SHUTDOWN_IMPLICIT_LOGO:
                node_printf(node, "%s received\n", efc_sm_event_name(evt));
                break;
        default:
                __efc_node_common(__func__, ctx, evt, arg);
        }
}

void
__efc_node_wait_ios_shutdown(struct efc_sm_ctx *ctx,
                             enum efc_sm_event evt, void *arg)
{
        struct efc_node *node = ctx->app;
        struct efc *efc = node->efc;

        efc_node_evt_set(ctx, evt, __func__);

        node_sm_trace();

        switch (evt) {
        case EFC_EVT_ENTER:
                efc_node_hold_frames(node);

                /* first check to see if no ELS IOs are outstanding */
                if (efc_els_io_list_empty(node, &node->els_ios_list))
                        /* If there are any active IOS, Free them. */
                        efc_node_transition(node, __efc_node_shutdown, NULL);
                break;

        case EFC_EVT_NODE_ACTIVE_IO_LIST_EMPTY:
        case EFC_EVT_ALL_CHILD_NODES_FREE:
                if (efc_els_io_list_empty(node, &node->els_ios_list))
                        efc_node_transition(node, __efc_node_shutdown, NULL);
                break;

        case EFC_EVT_EXIT:
                efc_node_accept_frames(node);
                break;

        case EFC_EVT_SRRS_ELS_REQ_FAIL:
                /* Can happen as ELS IO IO's complete */
                if (WARN_ON(!node->els_req_cnt))
                        break;
                node->els_req_cnt--;
                break;

        /* ignore shutdown events as we're already in shutdown path */
        case EFC_EVT_SHUTDOWN:
                /* have default shutdown event take precedence */
                node->shutdown_reason = EFC_NODE_SHUTDOWN_DEFAULT;
                fallthrough;

        case EFC_EVT_SHUTDOWN_EXPLICIT_LOGO:
        case EFC_EVT_SHUTDOWN_IMPLICIT_LOGO:
                efc_log_debug(efc, "[%s] %-20s\n", node->display_name,
                              efc_sm_event_name(evt));
                break;
        case EFC_EVT_DOMAIN_ATTACH_OK:
                /* don't care about domain_attach_ok */
                break;
        default:
                __efc_node_common(__func__, ctx, evt, arg);
        }
}

void
__efc_node_common(const char *funcname, struct efc_sm_ctx *ctx,
                  enum efc_sm_event evt, void *arg)
{
        struct efc_node *node = NULL;
        struct efc *efc = NULL;
        struct efc_node_cb *cbdata = arg;

        node = ctx->app;
        efc = node->efc;

        switch (evt) {
        case EFC_EVT_ENTER:
        case EFC_EVT_REENTER:
        case EFC_EVT_EXIT:
        case EFC_EVT_NPORT_TOPOLOGY_NOTIFY:
        case EFC_EVT_NODE_MISSING:
        case EFC_EVT_FCP_CMD_RCVD:
                break;

        case EFC_EVT_NODE_REFOUND:
                node->refound = true;
                break;

        /*
         * node->attached must be set appropriately
         * for all node attach/detach events
         */
        case EFC_EVT_NODE_ATTACH_OK:
                node->attached = true;
                break;

        case EFC_EVT_NODE_FREE_OK:
        case EFC_EVT_NODE_ATTACH_FAIL:
                node->attached = false;
                break;

        /*
         * handle any ELS completions that
         * other states either didn't care about
         * or forgot about
         */
        case EFC_EVT_SRRS_ELS_CMPL_OK:
        case EFC_EVT_SRRS_ELS_CMPL_FAIL:
                if (WARN_ON(!node->els_cmpl_cnt))
                        break;
                node->els_cmpl_cnt--;
                break;

        /*
         * handle any ELS request completions that
         * other states either didn't care about
         * or forgot about
         */
        case EFC_EVT_SRRS_ELS_REQ_OK:
        case EFC_EVT_SRRS_ELS_REQ_FAIL:
        case EFC_EVT_SRRS_ELS_REQ_RJT:
        case EFC_EVT_ELS_REQ_ABORTED:
                if (WARN_ON(!node->els_req_cnt))
                        break;
                node->els_req_cnt--;
                break;

        case EFC_EVT_ELS_RCVD: {
                struct fc_frame_header *hdr = cbdata->header->dma.virt;

                /*
                 * Unsupported ELS was received,
                 * send LS_RJT, command not supported
                 */
                efc_log_debug(efc,
                              "[%s] (%s) ELS x%02x, LS_RJT not supported\n",
                              node->display_name, funcname,
                              ((u8 *)cbdata->payload->dma.virt)[0]);

                efc_send_ls_rjt(node, be16_to_cpu(hdr->fh_ox_id),
                                ELS_RJT_UNSUP, ELS_EXPL_NONE, 0);
                break;
        }

        case EFC_EVT_PLOGI_RCVD:
        case EFC_EVT_FLOGI_RCVD:
        case EFC_EVT_LOGO_RCVD:
        case EFC_EVT_PRLI_RCVD:
        case EFC_EVT_PRLO_RCVD:
        case EFC_EVT_PDISC_RCVD:
        case EFC_EVT_FDISC_RCVD:
        case EFC_EVT_ADISC_RCVD:
        case EFC_EVT_RSCN_RCVD:
        case EFC_EVT_SCR_RCVD: {
                struct fc_frame_header *hdr = cbdata->header->dma.virt;

                /* sm: / send ELS_RJT */
                efc_log_debug(efc, "[%s] (%s) %s sending ELS_RJT\n",
                              node->display_name, funcname,
                              efc_sm_event_name(evt));
                /* if we didn't catch this in a state, send generic LS_RJT */
                efc_send_ls_rjt(node, be16_to_cpu(hdr->fh_ox_id),
                                ELS_RJT_UNAB, ELS_EXPL_NONE, 0);
                break;
        }
        case EFC_EVT_ABTS_RCVD: {
                efc_log_debug(efc, "[%s] (%s) %s sending BA_ACC\n",
                              node->display_name, funcname,
                              efc_sm_event_name(evt));

                /* sm: / send BA_ACC */
                efc_send_bls_acc(node, cbdata->header->dma.virt);
                break;
        }

        default:
                efc_log_debug(node->efc, "[%s] %-20s %-20s not handled\n",
                              node->display_name, funcname,
                              efc_sm_event_name(evt));
        }
}

void
efc_node_save_sparms(struct efc_node *node, void *payload)
{
        memcpy(node->service_params, payload, sizeof(node->service_params));
}

void
efc_node_post_event(struct efc_node *node,
                    enum efc_sm_event evt, void *arg)
{
        bool free_node = false;

        node->evtdepth++;

        efc_sm_post_event(&node->sm, evt, arg);

        /* If our event call depth is one and
         * we're not holding frames
         * then we can dispatch any pending frames.
         * We don't want to allow the efc_process_node_pending()
         * call to recurse.
         */
        if (!node->hold_frames && node->evtdepth == 1)
                efc_process_node_pending(node);

        node->evtdepth--;

        /*
         * Free the node object if so requested,
         * and we're at an event call depth of zero
         */
        if (node->evtdepth == 0 && node->req_free)
                free_node = true;

        if (free_node)
                efc_node_free(node);
}

void
efc_node_transition(struct efc_node *node,
                    void (*state)(struct efc_sm_ctx *,
                                  enum efc_sm_event, void *), void *data)
{
        struct efc_sm_ctx *ctx = &node->sm;

        if (ctx->current_state == state) {
                efc_node_post_event(node, EFC_EVT_REENTER, data);
        } else {
                efc_node_post_event(node, EFC_EVT_EXIT, data);
                ctx->current_state = state;
                efc_node_post_event(node, EFC_EVT_ENTER, data);
        }
}

void
efc_node_build_eui_name(char *buf, u32 buf_len, uint64_t eui_name)
{
        memset(buf, 0, buf_len);

        snprintf(buf, buf_len, "eui.%016llX", (unsigned long long)eui_name);
}

u64
efc_node_get_wwpn(struct efc_node *node)
{
        struct fc_els_flogi *sp =
                        (struct fc_els_flogi *)node->service_params;

        return be64_to_cpu(sp->fl_wwpn);
}

u64
efc_node_get_wwnn(struct efc_node *node)
{
        struct fc_els_flogi *sp =
                        (struct fc_els_flogi *)node->service_params;

        return be64_to_cpu(sp->fl_wwnn);
}

int
efc_node_check_els_req(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg,
                u8 cmd, void (*efc_node_common_func)(const char *,
                                struct efc_sm_ctx *, enum efc_sm_event, void *),
                const char *funcname)
{
        return 0;
}

int
efc_node_check_ns_req(struct efc_sm_ctx *ctx, enum efc_sm_event evt, void *arg,
                u16 cmd, void (*efc_node_common_func)(const char *,
                                struct efc_sm_ctx *, enum efc_sm_event, void *),
                const char *funcname)
{
        return 0;
}

int
efc_els_io_list_empty(struct efc_node *node, struct list_head *list)
{
        int empty;
        unsigned long flags = 0;

        spin_lock_irqsave(&node->els_ios_lock, flags);
        empty = list_empty(list);
        spin_unlock_irqrestore(&node->els_ios_lock, flags);
        return empty;
}

void
efc_node_pause(struct efc_node *node,
               void (*state)(struct efc_sm_ctx *,
                             enum efc_sm_event, void *))

{
        node->nodedb_state = state;
        efc_node_transition(node, __efc_node_paused, NULL);
}

void
__efc_node_paused(struct efc_sm_ctx *ctx,
                  enum efc_sm_event evt, void *arg)
{
        struct efc_node *node = ctx->app;

        efc_node_evt_set(ctx, evt, __func__);

        node_sm_trace();

        /*
         * This state is entered when a state is "paused". When resumed, the
         * node is transitioned to a previously saved state (node->ndoedb_state)
         */
        switch (evt) {
        case EFC_EVT_ENTER:
                node_printf(node, "Paused\n");
                break;

        case EFC_EVT_RESUME: {
                void (*pf)(struct efc_sm_ctx *ctx,
                           enum efc_sm_event evt, void *arg);

                pf = node->nodedb_state;

                node->nodedb_state = NULL;
                efc_node_transition(node, pf, NULL);
                break;
        }

        case EFC_EVT_DOMAIN_ATTACH_OK:
                break;

        case EFC_EVT_SHUTDOWN:
                node->req_free = true;
                break;

        default:
                __efc_node_common(__func__, ctx, evt, arg);
        }
}

void
efc_node_recv_els_frame(struct efc_node *node,
                        struct efc_hw_sequence *seq)
{
        u32 prli_size = sizeof(struct fc_els_prli) + sizeof(struct fc_els_spp);
        struct {
                u32 cmd;
                enum efc_sm_event evt;
                u32 payload_size;
        } els_cmd_list[] = {
                {ELS_PLOGI, EFC_EVT_PLOGI_RCVD, sizeof(struct fc_els_flogi)},
                {ELS_FLOGI, EFC_EVT_FLOGI_RCVD, sizeof(struct fc_els_flogi)},
                {ELS_LOGO, EFC_EVT_LOGO_RCVD, sizeof(struct fc_els_ls_acc)},
                {ELS_PRLI, EFC_EVT_PRLI_RCVD, prli_size},
                {ELS_PRLO, EFC_EVT_PRLO_RCVD, prli_size},
                {ELS_PDISC, EFC_EVT_PDISC_RCVD, MAX_ACC_REJECT_PAYLOAD},
                {ELS_FDISC, EFC_EVT_FDISC_RCVD, MAX_ACC_REJECT_PAYLOAD},
                {ELS_ADISC, EFC_EVT_ADISC_RCVD, sizeof(struct fc_els_adisc)},
                {ELS_RSCN, EFC_EVT_RSCN_RCVD, MAX_ACC_REJECT_PAYLOAD},
                {ELS_SCR, EFC_EVT_SCR_RCVD, MAX_ACC_REJECT_PAYLOAD},
        };
        struct efc_node_cb cbdata;
        u8 *buf = seq->payload->dma.virt;
        enum efc_sm_event evt = EFC_EVT_ELS_RCVD;
        u32 i;

        memset(&cbdata, 0, sizeof(cbdata));
        cbdata.header = seq->header;
        cbdata.payload = seq->payload;

        /* find a matching event for the ELS command */
        for (i = 0; i < ARRAY_SIZE(els_cmd_list); i++) {
                if (els_cmd_list[i].cmd == buf[0]) {
                        evt = els_cmd_list[i].evt;
                        break;
                }
        }

        efc_node_post_event(node, evt, &cbdata);
}

void
efc_node_recv_ct_frame(struct efc_node *node,
                       struct efc_hw_sequence *seq)
{
        struct fc_ct_hdr *iu = seq->payload->dma.virt;
        struct fc_frame_header *hdr = seq->header->dma.virt;
        struct efc *efc = node->efc;
        u16 gscmd = be16_to_cpu(iu->ct_cmd);

        efc_log_err(efc, "[%s] Received cmd :%x sending CT_REJECT\n",
                    node->display_name, gscmd);
        efc_send_ct_rsp(efc, node, be16_to_cpu(hdr->fh_ox_id), iu,
                        FC_FS_RJT, FC_FS_RJT_UNSUP, 0);
}

void
efc_node_recv_fcp_cmd(struct efc_node *node, struct efc_hw_sequence *seq)
{
        struct efc_node_cb cbdata;

        memset(&cbdata, 0, sizeof(cbdata));
        cbdata.header = seq->header;
        cbdata.payload = seq->payload;

        efc_node_post_event(node, EFC_EVT_FCP_CMD_RCVD, &cbdata);
}

void
efc_process_node_pending(struct efc_node *node)
{
        struct efc *efc = node->efc;
        struct efc_hw_sequence *seq = NULL;
        u32 pend_frames_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 (node->hold_frames)
                        break;

                seq = NULL;
                /* Get next frame/sequence */
                spin_lock_irqsave(&node->pend_frames_lock, flags);

                if (!list_empty(&node->pend_frames)) {
                        seq = list_first_entry(&node->pend_frames,
                                        struct efc_hw_sequence, list_entry);
                        list_del(&seq->list_entry);
                }
                spin_unlock_irqrestore(&node->pend_frames_lock, flags);

                if (!seq) {
                        pend_frames_processed = node->pend_frames_processed;
                        node->pend_frames_processed = 0;
                        break;
                }
                node->pend_frames_processed++;

                /* now dispatch frame(s) to dispatch function */
                efc_node_dispatch_frame(node, seq);
                efc->tt.hw_seq_free(efc, seq);
        }

        if (pend_frames_processed != 0)
                efc_log_debug(efc, "%u node frames held and processed\n",
                              pend_frames_processed);
}

void
efc_scsi_sess_reg_complete(struct efc_node *node, u32 status)
{
        unsigned long flags = 0;
        enum efc_sm_event evt = EFC_EVT_NODE_SESS_REG_OK;
        struct efc *efc = node->efc;

        if (status)
                evt = EFC_EVT_NODE_SESS_REG_FAIL;

        spin_lock_irqsave(&efc->lock, flags);
        /* Notify the node to resume */
        efc_node_post_event(node, evt, NULL);
        spin_unlock_irqrestore(&efc->lock, flags);
}

void
efc_scsi_del_initiator_complete(struct efc *efc, struct efc_node *node)
{
        unsigned long flags = 0;

        spin_lock_irqsave(&efc->lock, flags);
        /* Notify the node to resume */
        efc_node_post_event(node, EFC_EVT_NODE_DEL_INI_COMPLETE, NULL);
        spin_unlock_irqrestore(&efc->lock, flags);
}

void
efc_scsi_del_target_complete(struct efc *efc, struct efc_node *node)
{
        unsigned long flags = 0;

        spin_lock_irqsave(&efc->lock, flags);
        /* Notify the node to resume */
        efc_node_post_event(node, EFC_EVT_NODE_DEL_TGT_COMPLETE, NULL);
        spin_unlock_irqrestore(&efc->lock, flags);
}

void
efc_scsi_io_list_empty(struct efc *efc, struct efc_node *node)
{
        unsigned long flags = 0;

        spin_lock_irqsave(&efc->lock, flags);
        efc_node_post_event(node, EFC_EVT_NODE_ACTIVE_IO_LIST_EMPTY, NULL);
        spin_unlock_irqrestore(&efc->lock, flags);
}

void efc_node_post_els_resp(struct efc_node *node, u32 evt, void *arg)
{
        struct efc *efc = node->efc;
        unsigned long flags = 0;

        spin_lock_irqsave(&efc->lock, flags);
        efc_node_post_event(node, evt, arg);
        spin_unlock_irqrestore(&efc->lock, flags);
}

void efc_node_post_shutdown(struct efc_node *node, void *arg)
{
        unsigned long flags = 0;
        struct efc *efc = node->efc;

        spin_lock_irqsave(&efc->lock, flags);
        efc_node_post_event(node, EFC_EVT_SHUTDOWN, arg);
        spin_unlock_irqrestore(&efc->lock, flags);
}