root/drivers/scsi/qla2xxx/qla_edif.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Marvell Fibre Channel HBA Driver
 * Copyright (c)  2021     Marvell
 */
#include "qla_def.h"
#include "qla_edif.h"

#include <linux/kthread.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <scsi/scsi_tcq.h>

static struct edif_sa_index_entry *qla_edif_sadb_find_sa_index_entry(uint16_t nport_handle,
                struct list_head *sa_list);
static uint16_t qla_edif_sadb_get_sa_index(fc_port_t *fcport,
                struct qla_sa_update_frame *sa_frame);
static int qla_edif_sadb_delete_sa_index(fc_port_t *fcport, uint16_t nport_handle,
                uint16_t sa_index);
static int qla_pur_get_pending(scsi_qla_host_t *, fc_port_t *, struct bsg_job *);

struct edb_node {
        struct  list_head       list;
        uint32_t                ntype;
        union {
                port_id_t       plogi_did;
                uint32_t        async;
                port_id_t       els_sid;
                struct edif_sa_update_aen       sa_aen;
        } u;
};

static struct els_sub_cmd {
        uint16_t cmd;
        const char *str;
} sc_str[] = {
        {SEND_ELS, "send ELS"},
        {SEND_ELS_REPLY, "send ELS Reply"},
        {PULL_ELS, "retrieve ELS"},
};

const char *sc_to_str(uint16_t cmd)
{
        int i;
        struct els_sub_cmd *e;

        for (i = 0; i < ARRAY_SIZE(sc_str); i++) {
                e = sc_str + i;
                if (cmd == e->cmd)
                        return e->str;
        }
        return "unknown";
}

static struct edb_node *qla_edb_getnext(scsi_qla_host_t *vha)
{
        unsigned long   flags;
        struct edb_node *edbnode = NULL;

        spin_lock_irqsave(&vha->e_dbell.db_lock, flags);

        /* db nodes are fifo - no qualifications done */
        if (!list_empty(&vha->e_dbell.head)) {
                edbnode = list_first_entry(&vha->e_dbell.head,
                                           struct edb_node, list);
                list_del_init(&edbnode->list);
        }

        spin_unlock_irqrestore(&vha->e_dbell.db_lock, flags);

        return edbnode;
}

static void qla_edb_node_free(scsi_qla_host_t *vha, struct edb_node *node)
{
        list_del_init(&node->list);
        kfree(node);
}

static struct edif_list_entry *qla_edif_list_find_sa_index(fc_port_t *fcport,
                uint16_t handle)
{
        struct edif_list_entry *entry;
        struct edif_list_entry *tentry;
        struct list_head *indx_list = &fcport->edif.edif_indx_list;

        list_for_each_entry_safe(entry, tentry, indx_list, next) {
                if (entry->handle == handle)
                        return entry;
        }
        return NULL;
}

/* timeout called when no traffic and delayed rx sa_index delete */
static void qla2x00_sa_replace_iocb_timeout(struct timer_list *t)
{
        struct edif_list_entry *edif_entry = timer_container_of(edif_entry, t,
                                                                timer);
        fc_port_t *fcport = edif_entry->fcport;
        struct scsi_qla_host *vha = fcport->vha;
        struct  edif_sa_ctl *sa_ctl;
        uint16_t nport_handle;
        unsigned long flags = 0;

        ql_dbg(ql_dbg_edif, vha, 0x3069,
            "%s:  nport_handle 0x%x,  SA REPL Delay Timeout, %8phC portid=%06x\n",
            __func__, edif_entry->handle, fcport->port_name, fcport->d_id.b24);

        /*
         * if delete_sa_index is valid then no one has serviced this
         * delayed delete
         */
        spin_lock_irqsave(&fcport->edif.indx_list_lock, flags);

        /*
         * delete_sa_index is invalidated when we find the new sa_index in
         * the incoming data stream.  If it is not invalidated then we are
         * still looking for the new sa_index because there is no I/O and we
         * need to just force the rx delete and move on.  Otherwise
         * we could get another rekey which will result in an error 66.
         */
        if (edif_entry->delete_sa_index != INVALID_EDIF_SA_INDEX) {
                uint16_t delete_sa_index = edif_entry->delete_sa_index;

                edif_entry->delete_sa_index = INVALID_EDIF_SA_INDEX;
                nport_handle = edif_entry->handle;
                spin_unlock_irqrestore(&fcport->edif.indx_list_lock, flags);

                sa_ctl = qla_edif_find_sa_ctl_by_index(fcport,
                    delete_sa_index, 0);

                if (sa_ctl) {
                        ql_dbg(ql_dbg_edif, vha, 0x3063,
                            "%s: sa_ctl: %p, delete index %d, update index: %d, lid: 0x%x\n",
                            __func__, sa_ctl, delete_sa_index, edif_entry->update_sa_index,
                            nport_handle);

                        sa_ctl->flags = EDIF_SA_CTL_FLG_DEL;
                        set_bit(EDIF_SA_CTL_REPL, &sa_ctl->state);
                        qla_post_sa_replace_work(fcport->vha, fcport,
                            nport_handle, sa_ctl);

                } else {
                        ql_dbg(ql_dbg_edif, vha, 0x3063,
                            "%s: sa_ctl not found for delete_sa_index: %d\n",
                            __func__, edif_entry->delete_sa_index);
                }
        } else {
                spin_unlock_irqrestore(&fcport->edif.indx_list_lock, flags);
        }
}

/*
 * create a new list entry for this nport handle and
 * add an sa_update index to the list - called for sa_update
 */
static int qla_edif_list_add_sa_update_index(fc_port_t *fcport,
                uint16_t sa_index, uint16_t handle)
{
        struct edif_list_entry *entry;
        unsigned long flags = 0;

        /* if the entry exists, then just update the sa_index */
        entry = qla_edif_list_find_sa_index(fcport, handle);
        if (entry) {
                entry->update_sa_index = sa_index;
                entry->count = 0;
                return 0;
        }

        /*
         * This is the normal path - there should be no existing entry
         * when update is called.  The exception is at startup
         * when update is called for the first two sa_indexes
         * followed by a delete of the first sa_index
         */
        entry = kzalloc_obj(struct edif_list_entry, GFP_ATOMIC);
        if (!entry)
                return -ENOMEM;

        INIT_LIST_HEAD(&entry->next);
        entry->handle = handle;
        entry->update_sa_index = sa_index;
        entry->delete_sa_index = INVALID_EDIF_SA_INDEX;
        entry->count = 0;
        entry->flags = 0;
        timer_setup(&entry->timer, qla2x00_sa_replace_iocb_timeout, 0);
        spin_lock_irqsave(&fcport->edif.indx_list_lock, flags);
        list_add_tail(&entry->next, &fcport->edif.edif_indx_list);
        spin_unlock_irqrestore(&fcport->edif.indx_list_lock, flags);
        return 0;
}

/* remove an entry from the list */
static void qla_edif_list_delete_sa_index(fc_port_t *fcport, struct edif_list_entry *entry)
{
        unsigned long flags = 0;

        spin_lock_irqsave(&fcport->edif.indx_list_lock, flags);
        list_del(&entry->next);
        spin_unlock_irqrestore(&fcport->edif.indx_list_lock, flags);
}

int qla_post_sa_replace_work(struct scsi_qla_host *vha,
         fc_port_t *fcport, uint16_t nport_handle, struct edif_sa_ctl *sa_ctl)
{
        struct qla_work_evt *e;

        e = qla2x00_alloc_work(vha, QLA_EVT_SA_REPLACE);
        if (!e)
                return QLA_FUNCTION_FAILED;

        e->u.sa_update.fcport = fcport;
        e->u.sa_update.sa_ctl = sa_ctl;
        e->u.sa_update.nport_handle = nport_handle;
        fcport->flags |= FCF_ASYNC_ACTIVE;
        return qla2x00_post_work(vha, e);
}

static void
qla_edif_sa_ctl_init(scsi_qla_host_t *vha, struct fc_port  *fcport)
{
        ql_dbg(ql_dbg_edif, vha, 0x2058,
            "Init SA_CTL List for fcport - nn %8phN pn %8phN portid=%06x.\n",
            fcport->node_name, fcport->port_name, fcport->d_id.b24);

        fcport->edif.tx_rekey_cnt = 0;
        fcport->edif.rx_rekey_cnt = 0;

        fcport->edif.tx_bytes = 0;
        fcport->edif.rx_bytes = 0;
}

static int qla_bsg_check(scsi_qla_host_t *vha, struct bsg_job *bsg_job,
fc_port_t *fcport)
{
        struct extra_auth_els *p;
        struct fc_bsg_reply *bsg_reply = bsg_job->reply;
        struct qla_bsg_auth_els_request *req =
            (struct qla_bsg_auth_els_request *)bsg_job->request;

        if (!vha->hw->flags.edif_enabled) {
                ql_dbg(ql_dbg_edif, vha, 0x9105,
                    "%s edif not enabled\n", __func__);
                goto done;
        }
        if (DBELL_INACTIVE(vha)) {
                ql_dbg(ql_dbg_edif, vha, 0x09102,
                    "%s doorbell not enabled\n", __func__);
                goto done;
        }

        p = &req->e;

        /* Get response */
        if (p->sub_cmd == PULL_ELS) {
                struct qla_bsg_auth_els_reply *rpl =
                        (struct qla_bsg_auth_els_reply *)bsg_job->reply;

                qla_pur_get_pending(vha, fcport, bsg_job);

                ql_dbg(ql_dbg_edif, vha, 0x911d,
                        "%s %s %8phN sid=%x. xchg %x, nb=%xh bsg ptr %p\n",
                        __func__, sc_to_str(p->sub_cmd), fcport->port_name,
                        fcport->d_id.b24, rpl->rx_xchg_address,
                        rpl->r.reply_payload_rcv_len, bsg_job);

                goto done;
        }
        return 0;

done:

        bsg_job_done(bsg_job, bsg_reply->result,
                        bsg_reply->reply_payload_rcv_len);
        return -EIO;
}

fc_port_t *
qla2x00_find_fcport_by_pid(scsi_qla_host_t *vha, port_id_t *id)
{
        fc_port_t *f, *tf;

        f = NULL;
        list_for_each_entry_safe(f, tf, &vha->vp_fcports, list) {
                if (f->d_id.b24 == id->b24)
                        return f;
        }
        return NULL;
}

/**
 * qla_edif_app_check(): check for valid application id.
 * @vha: host adapter pointer
 * @appid: application id
 * Return: false = fail, true = pass
 */
static bool
qla_edif_app_check(scsi_qla_host_t *vha, struct app_id appid)
{
        /* check that the app is allow/known to the driver */

        if (appid.app_vid != EDIF_APP_ID) {
                ql_dbg(ql_dbg_edif, vha, 0x911d, "%s app id not ok (%x)",
                    __func__, appid.app_vid);
                return false;
        }

        if (appid.version != EDIF_VERSION1) {
                ql_dbg(ql_dbg_edif, vha, 0x911d, "%s app version is not ok (%x)",
                    __func__, appid.version);
                return false;
        }

        return true;
}

static void
qla_edif_free_sa_ctl(fc_port_t *fcport, struct edif_sa_ctl *sa_ctl,
        int index)
{
        unsigned long flags = 0;

        spin_lock_irqsave(&fcport->edif.sa_list_lock, flags);
        list_del(&sa_ctl->next);
        spin_unlock_irqrestore(&fcport->edif.sa_list_lock, flags);
        if (index >= 512)
                fcport->edif.tx_rekey_cnt--;
        else
                fcport->edif.rx_rekey_cnt--;
        kfree(sa_ctl);
}

/* return an index to the freepool */
static void qla_edif_add_sa_index_to_freepool(fc_port_t *fcport, int dir,
                uint16_t sa_index)
{
        void *sa_id_map;
        struct scsi_qla_host *vha = fcport->vha;
        struct qla_hw_data *ha = vha->hw;
        unsigned long flags = 0;
        u16 lsa_index = sa_index;

        ql_dbg(ql_dbg_edif + ql_dbg_verbose, vha, 0x3063,
            "%s: entry\n", __func__);

        if (dir) {
                sa_id_map = ha->edif_tx_sa_id_map;
                lsa_index -= EDIF_TX_SA_INDEX_BASE;
        } else {
                sa_id_map = ha->edif_rx_sa_id_map;
        }

        spin_lock_irqsave(&ha->sadb_fp_lock, flags);
        clear_bit(lsa_index, sa_id_map);
        spin_unlock_irqrestore(&ha->sadb_fp_lock, flags);
        ql_dbg(ql_dbg_edif, vha, 0x3063,
            "%s: index %d added to free pool\n", __func__, sa_index);
}

static void __qla2x00_release_all_sadb(struct scsi_qla_host *vha,
        struct fc_port *fcport, struct edif_sa_index_entry *entry,
        int pdir)
{
        struct edif_list_entry *edif_entry;
        struct  edif_sa_ctl *sa_ctl;
        int i, dir;
        int key_cnt = 0;

        for (i = 0; i < 2; i++) {
                if (entry->sa_pair[i].sa_index == INVALID_EDIF_SA_INDEX)
                        continue;

                if (fcport->loop_id != entry->handle) {
                        ql_dbg(ql_dbg_edif, vha, 0x3063,
                            "%s: ** WARNING %d** entry handle: 0x%x, lid: 0x%x, sa_index: %d\n",
                            __func__, i, entry->handle, fcport->loop_id,
                            entry->sa_pair[i].sa_index);
                }

                /* release the sa_ctl */
                sa_ctl = qla_edif_find_sa_ctl_by_index(fcport,
                                entry->sa_pair[i].sa_index, pdir);
                if (sa_ctl &&
                    qla_edif_find_sa_ctl_by_index(fcport, sa_ctl->index, pdir)) {
                        ql_dbg(ql_dbg_edif, vha, 0x3063,
                            "%s: freeing sa_ctl for index %d\n", __func__, sa_ctl->index);
                        qla_edif_free_sa_ctl(fcport, sa_ctl, sa_ctl->index);
                } else {
                        ql_dbg(ql_dbg_edif, vha, 0x3063,
                            "%s: sa_ctl NOT freed, sa_ctl: %p\n", __func__, sa_ctl);
                }

                /* Release the index */
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                        "%s: freeing sa_index %d, nph: 0x%x\n",
                        __func__, entry->sa_pair[i].sa_index, entry->handle);

                dir = (entry->sa_pair[i].sa_index <
                        EDIF_TX_SA_INDEX_BASE) ? 0 : 1;
                qla_edif_add_sa_index_to_freepool(fcport, dir,
                        entry->sa_pair[i].sa_index);

                /* Delete timer on RX */
                if (pdir != SAU_FLG_TX) {
                        edif_entry =
                                qla_edif_list_find_sa_index(fcport, entry->handle);
                        if (edif_entry) {
                                ql_dbg(ql_dbg_edif, vha, 0x5033,
                                    "%s: remove edif_entry %p, update_sa_index: 0x%x, delete_sa_index: 0x%x\n",
                                    __func__, edif_entry, edif_entry->update_sa_index,
                                    edif_entry->delete_sa_index);
                                qla_edif_list_delete_sa_index(fcport, edif_entry);
                                /*
                                 * valid delete_sa_index indicates there is a rx
                                 * delayed delete queued
                                 */
                                if (edif_entry->delete_sa_index !=
                                                INVALID_EDIF_SA_INDEX) {
                                        timer_shutdown(&edif_entry->timer);

                                        /* build and send the aen */
                                        fcport->edif.rx_sa_set = 1;
                                        fcport->edif.rx_sa_pending = 0;
                                        qla_edb_eventcreate(vha,
                                                        VND_CMD_AUTH_STATE_SAUPDATE_COMPL,
                                                        QL_VND_SA_STAT_SUCCESS,
                                                        QL_VND_RX_SA_KEY, fcport);
                                }
                                ql_dbg(ql_dbg_edif, vha, 0x5033,
                                    "%s: release edif_entry %p, update_sa_index: 0x%x, delete_sa_index: 0x%x\n",
                                    __func__, edif_entry, edif_entry->update_sa_index,
                                    edif_entry->delete_sa_index);

                                kfree(edif_entry);
                        }
                }
                key_cnt++;
        }
        ql_dbg(ql_dbg_edif, vha, 0x3063,
            "%s: %d %s keys released\n",
            __func__, key_cnt, pdir ? "tx" : "rx");
}

/* find an release all outstanding sadb sa_indicies */
void qla2x00_release_all_sadb(struct scsi_qla_host *vha, struct fc_port *fcport)
{
        struct edif_sa_index_entry *entry, *tmp;
        struct qla_hw_data *ha = vha->hw;
        unsigned long flags;

        ql_dbg(ql_dbg_edif + ql_dbg_verbose, vha, 0x3063,
            "%s: Starting...\n", __func__);

        spin_lock_irqsave(&ha->sadb_lock, flags);

        list_for_each_entry_safe(entry, tmp, &ha->sadb_rx_index_list, next) {
                if (entry->fcport == fcport) {
                        list_del(&entry->next);
                        spin_unlock_irqrestore(&ha->sadb_lock, flags);
                        __qla2x00_release_all_sadb(vha, fcport, entry, 0);
                        kfree(entry);
                        spin_lock_irqsave(&ha->sadb_lock, flags);
                        break;
                }
        }

        list_for_each_entry_safe(entry, tmp, &ha->sadb_tx_index_list, next) {
                if (entry->fcport == fcport) {
                        list_del(&entry->next);
                        spin_unlock_irqrestore(&ha->sadb_lock, flags);

                        __qla2x00_release_all_sadb(vha, fcport, entry, SAU_FLG_TX);

                        kfree(entry);
                        spin_lock_irqsave(&ha->sadb_lock, flags);
                        break;
                }
        }
        spin_unlock_irqrestore(&ha->sadb_lock, flags);
}

/**
 * qla_delete_n2n_sess_and_wait: search for N2N session, tear it down and
 *    wait for tear down to complete.  In N2N topology, there is only one
 *    session being active in tracking the remote device.
 * @vha: host adapter pointer
 * return code:  0 - found the session and completed the tear down.
 *      1 - timeout occurred.  Caller to use link bounce to reset.
 */
static int qla_delete_n2n_sess_and_wait(scsi_qla_host_t *vha)
{
        struct fc_port *fcport;
        int rc = -EIO;
        ulong expire = jiffies + 23 * HZ;

        if (!N2N_TOPO(vha->hw))
                return 0;

        fcport = NULL;
        list_for_each_entry(fcport, &vha->vp_fcports, list) {
                if (!fcport->n2n_flag)
                        continue;

                ql_dbg(ql_dbg_disc, fcport->vha, 0x2016,
                       "%s reset sess at app start \n", __func__);

                qla_edif_sa_ctl_init(vha, fcport);
                qlt_schedule_sess_for_deletion(fcport);

                while (time_before_eq(jiffies, expire)) {
                        if (fcport->disc_state != DSC_DELETE_PEND) {
                                rc = 0;
                                break;
                        }
                        msleep(1);
                }

                set_bit(RELOGIN_NEEDED, &vha->dpc_flags);
                break;
        }

        return rc;
}

/**
 * qla_edif_app_start:  application has announce its present
 * @vha: host adapter pointer
 * @bsg_job: user request
 *
 * Set/activate doorbell.  Reset current sessions and re-login with
 * secure flag.
 */
static int
qla_edif_app_start(scsi_qla_host_t *vha, struct bsg_job *bsg_job)
{
        int32_t                 rval = 0;
        struct fc_bsg_reply     *bsg_reply = bsg_job->reply;
        struct app_start        appstart;
        struct app_start_reply  appreply;
        struct fc_port  *fcport, *tf;

        ql_log(ql_log_info, vha, 0x1313,
               "EDIF application registration with driver, FC device connections will be re-established.\n");

        sg_copy_to_buffer(bsg_job->request_payload.sg_list,
            bsg_job->request_payload.sg_cnt, &appstart,
            sizeof(struct app_start));

        ql_dbg(ql_dbg_edif, vha, 0x911d, "%s app_vid=%x app_start_flags %x\n",
             __func__, appstart.app_info.app_vid, appstart.app_start_flags);

        if (DBELL_INACTIVE(vha)) {
                /* mark doorbell as active since an app is now present */
                vha->e_dbell.db_flags |= EDB_ACTIVE;
        } else {
                goto out;
        }

        if (N2N_TOPO(vha->hw)) {
                list_for_each_entry_safe(fcport, tf, &vha->vp_fcports, list)
                        fcport->n2n_link_reset_cnt = 0;

                if (vha->hw->flags.n2n_fw_acc_sec) {
                        bool link_bounce = false;
                        /*
                         * While authentication app was not running, remote device
                         * could still try to login with this local port.  Let's
                         * reset the session, reconnect and re-authenticate.
                         */
                        if (qla_delete_n2n_sess_and_wait(vha))
                                link_bounce = true;

                        /* bounce the link to start login */
                        if (!vha->hw->flags.n2n_bigger || link_bounce) {
                                set_bit(N2N_LINK_RESET, &vha->dpc_flags);
                                qla2xxx_wake_dpc(vha);
                        }
                } else {
                        qla2x00_wait_for_hba_online(vha);
                        set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags);
                        qla2xxx_wake_dpc(vha);
                        qla2x00_wait_for_hba_online(vha);
                }
        } else {
                list_for_each_entry_safe(fcport, tf, &vha->vp_fcports, list) {
                        ql_dbg(ql_dbg_edif, vha, 0x2058,
                               "FCSP - nn %8phN pn %8phN portid=%06x.\n",
                               fcport->node_name, fcport->port_name,
                               fcport->d_id.b24);
                        ql_dbg(ql_dbg_edif, vha, 0xf084,
                               "%s: se_sess %p / sess %p from port %8phC "
                               "loop_id %#04x s_id %06x logout %d "
                               "keep %d els_logo %d disc state %d auth state %d"
                               "stop state %d\n",
                               __func__, fcport->se_sess, fcport,
                               fcport->port_name, fcport->loop_id,
                               fcport->d_id.b24, fcport->logout_on_delete,
                               fcport->keep_nport_handle, fcport->send_els_logo,
                               fcport->disc_state, fcport->edif.auth_state,
                               fcport->edif.app_stop);

                        if (atomic_read(&vha->loop_state) == LOOP_DOWN)
                                break;

                        fcport->login_retry = vha->hw->login_retry_count;

                        fcport->edif.app_stop = 0;
                        fcport->edif.app_sess_online = 0;

                        if (fcport->scan_state != QLA_FCPORT_FOUND)
                                continue;

                        if (fcport->port_type == FCT_UNKNOWN &&
                            !fcport->fc4_features)
                                rval = qla24xx_async_gffid(vha, fcport, true);

                        if (!rval && !(fcport->fc4_features & FC4_FF_TARGET ||
                            fcport->port_type & (FCT_TARGET|FCT_NVME_TARGET)))
                                continue;

                        rval = 0;

                        ql_dbg(ql_dbg_edif, vha, 0x911e,
                               "%s wwpn %8phC calling qla_edif_reset_auth_wait\n",
                               __func__, fcport->port_name);
                        qlt_schedule_sess_for_deletion(fcport);
                        qla_edif_sa_ctl_init(vha, fcport);
                }
                set_bit(RELOGIN_NEEDED, &vha->dpc_flags);
        }

        if (vha->pur_cinfo.enode_flags != ENODE_ACTIVE) {
                /* mark as active since an app is now present */
                vha->pur_cinfo.enode_flags = ENODE_ACTIVE;
        } else {
                ql_dbg(ql_dbg_edif, vha, 0x911f, "%s enode already active\n",
                     __func__);
        }

out:
        appreply.host_support_edif = vha->hw->flags.edif_enabled;
        appreply.edif_enode_active = vha->pur_cinfo.enode_flags;
        appreply.edif_edb_active = vha->e_dbell.db_flags;
        appreply.version = EDIF_VERSION1;

        bsg_job->reply_len = sizeof(struct fc_bsg_reply);

        SET_DID_STATUS(bsg_reply->result, DID_OK);

        bsg_reply->reply_payload_rcv_len = sg_copy_from_buffer(bsg_job->reply_payload.sg_list,
                                                               bsg_job->reply_payload.sg_cnt,
                                                               &appreply,
                                                               sizeof(struct app_start_reply));

        ql_dbg(ql_dbg_edif, vha, 0x911d,
            "%s app start completed with 0x%x\n",
            __func__, rval);

        return rval;
}

/**
 * qla_edif_app_stop - app has announced it's exiting.
 * @vha: host adapter pointer
 * @bsg_job: user space command pointer
 *
 * Free any in flight messages, clear all doorbell events
 * to application. Reject any message relate to security.
 */
static int
qla_edif_app_stop(scsi_qla_host_t *vha, struct bsg_job *bsg_job)
{
        struct app_stop         appstop;
        struct fc_bsg_reply     *bsg_reply = bsg_job->reply;
        struct fc_port  *fcport, *tf;

        sg_copy_to_buffer(bsg_job->request_payload.sg_list,
            bsg_job->request_payload.sg_cnt, &appstop,
            sizeof(struct app_stop));

        ql_dbg(ql_dbg_edif, vha, 0x911d, "%s Stopping APP: app_vid=%x\n",
            __func__, appstop.app_info.app_vid);

        /* Call db stop and enode stop functions */

        /* if we leave this running short waits are operational < 16 secs */
        qla_enode_stop(vha);        /* stop enode */
        qla_edb_stop(vha);          /* stop db */

        list_for_each_entry_safe(fcport, tf, &vha->vp_fcports, list) {
                if (!(fcport->flags & FCF_FCSP_DEVICE))
                        continue;

                if (fcport->flags & FCF_FCSP_DEVICE) {
                        ql_dbg(ql_dbg_edif, vha, 0xf084,
                            "%s: sess %p from port %8phC lid %#04x s_id %06x logout %d keep %d els_logo %d\n",
                            __func__, fcport,
                            fcport->port_name, fcport->loop_id, fcport->d_id.b24,
                            fcport->logout_on_delete, fcport->keep_nport_handle,
                            fcport->send_els_logo);

                        if (atomic_read(&vha->loop_state) == LOOP_DOWN)
                                break;

                        fcport->edif.app_stop = 1;
                        ql_dbg(ql_dbg_edif, vha, 0x911e,
                                "%s wwpn %8phC calling qla_edif_reset_auth_wait\n",
                                __func__, fcport->port_name);

                        fcport->send_els_logo = 1;
                        qlt_schedule_sess_for_deletion(fcport);
                }
        }

        bsg_job->reply_len = sizeof(struct fc_bsg_reply);
        SET_DID_STATUS(bsg_reply->result, DID_OK);

        /* no return interface to app - it assumes we cleaned up ok */

        return 0;
}

static int
qla_edif_app_chk_sa_update(scsi_qla_host_t *vha, fc_port_t *fcport,
                struct app_plogi_reply *appplogireply)
{
        int     ret = 0;

        if (!(fcport->edif.rx_sa_set && fcport->edif.tx_sa_set)) {
                ql_dbg(ql_dbg_edif, vha, 0x911e,
                    "%s: wwpn %8phC Both SA indexes has not been SET TX %d, RX %d.\n",
                    __func__, fcport->port_name, fcport->edif.tx_sa_set,
                    fcport->edif.rx_sa_set);
                appplogireply->prli_status = 0;
                ret = 1;
        } else  {
                ql_dbg(ql_dbg_edif, vha, 0x911e,
                    "%s wwpn %8phC Both SA(s) updated.\n", __func__,
                    fcport->port_name);
                fcport->edif.rx_sa_set = fcport->edif.tx_sa_set = 0;
                fcport->edif.rx_sa_pending = fcport->edif.tx_sa_pending = 0;
                appplogireply->prli_status = 1;
        }
        return ret;
}

/**
 * qla_edif_app_authok - authentication by app succeeded.  Driver can proceed
 *   with prli
 * @vha: host adapter pointer
 * @bsg_job: user request
 */
static int
qla_edif_app_authok(scsi_qla_host_t *vha, struct bsg_job *bsg_job)
{
        struct auth_complete_cmd appplogiok;
        struct app_plogi_reply  appplogireply = {0};
        struct fc_bsg_reply     *bsg_reply = bsg_job->reply;
        fc_port_t               *fcport = NULL;
        port_id_t               portid = {0};

        sg_copy_to_buffer(bsg_job->request_payload.sg_list,
            bsg_job->request_payload.sg_cnt, &appplogiok,
            sizeof(struct auth_complete_cmd));

        /* silent unaligned access warning */
        portid.b.domain = appplogiok.u.d_id.b.domain;
        portid.b.area   = appplogiok.u.d_id.b.area;
        portid.b.al_pa  = appplogiok.u.d_id.b.al_pa;

        appplogireply.version = EDIF_VERSION1;
        switch (appplogiok.type) {
        case PL_TYPE_WWPN:
                fcport = qla2x00_find_fcport_by_wwpn(vha,
                    appplogiok.u.wwpn, 0);
                if (!fcport)
                        ql_dbg(ql_dbg_edif, vha, 0x911d,
                            "%s wwpn lookup failed: %8phC\n",
                            __func__, appplogiok.u.wwpn);
                break;
        case PL_TYPE_DID:
                fcport = qla2x00_find_fcport_by_pid(vha, &portid);
                if (!fcport)
                        ql_dbg(ql_dbg_edif, vha, 0x911d,
                            "%s d_id lookup failed: %x\n", __func__,
                            portid.b24);
                break;
        default:
                ql_dbg(ql_dbg_edif, vha, 0x911d,
                    "%s undefined type: %x\n", __func__,
                    appplogiok.type);
                break;
        }

        if (!fcport) {
                SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                goto errstate_exit;
        }

        /*
         * if port is online then this is a REKEY operation
         * Only do sa update checking
         */
        if (atomic_read(&fcport->state) == FCS_ONLINE) {
                ql_dbg(ql_dbg_edif, vha, 0x911d,
                    "%s Skipping PRLI complete based on rekey\n", __func__);
                appplogireply.prli_status = 1;
                SET_DID_STATUS(bsg_reply->result, DID_OK);
                qla_edif_app_chk_sa_update(vha, fcport, &appplogireply);
                goto errstate_exit;
        }

        /* make sure in AUTH_PENDING or else reject */
        if (fcport->disc_state != DSC_LOGIN_AUTH_PEND) {
                ql_dbg(ql_dbg_edif, vha, 0x911e,
                    "%s wwpn %8phC is not in auth pending state (%x)\n",
                    __func__, fcport->port_name, fcport->disc_state);
                SET_DID_STATUS(bsg_reply->result, DID_OK);
                appplogireply.prli_status = 0;
                goto errstate_exit;
        }

        SET_DID_STATUS(bsg_reply->result, DID_OK);
        appplogireply.prli_status = 1;
        fcport->edif.authok = 1;
        if (!(fcport->edif.rx_sa_set && fcport->edif.tx_sa_set)) {
                ql_dbg(ql_dbg_edif, vha, 0x911e,
                    "%s: wwpn %8phC Both SA indexes has not been SET TX %d, RX %d.\n",
                    __func__, fcport->port_name, fcport->edif.tx_sa_set,
                    fcport->edif.rx_sa_set);
                SET_DID_STATUS(bsg_reply->result, DID_OK);
                appplogireply.prli_status = 0;
                goto errstate_exit;

        } else {
                ql_dbg(ql_dbg_edif, vha, 0x911e,
                    "%s wwpn %8phC Both SA(s) updated.\n", __func__,
                    fcport->port_name);
                fcport->edif.rx_sa_set = fcport->edif.tx_sa_set = 0;
                fcport->edif.rx_sa_pending = fcport->edif.tx_sa_pending = 0;
        }

        if (qla_ini_mode_enabled(vha)) {
                ql_dbg(ql_dbg_edif, vha, 0x911e,
                    "%s AUTH complete - RESUME with prli for wwpn %8phC\n",
                    __func__, fcport->port_name);
                qla24xx_post_prli_work(vha, fcport);
        }

errstate_exit:
        bsg_job->reply_len = sizeof(struct fc_bsg_reply);
        bsg_reply->reply_payload_rcv_len = sg_copy_from_buffer(bsg_job->reply_payload.sg_list,
                                                               bsg_job->reply_payload.sg_cnt,
                                                               &appplogireply,
                                                               sizeof(struct app_plogi_reply));

        return 0;
}

/**
 * qla_edif_app_authfail - authentication by app has failed.  Driver is given
 *   notice to tear down current session.
 * @vha: host adapter pointer
 * @bsg_job: user request
 */
static int
qla_edif_app_authfail(scsi_qla_host_t *vha, struct bsg_job *bsg_job)
{
        int32_t                 rval = 0;
        struct auth_complete_cmd appplogifail;
        struct fc_bsg_reply     *bsg_reply = bsg_job->reply;
        fc_port_t               *fcport = NULL;
        port_id_t               portid = {0};

        ql_dbg(ql_dbg_edif, vha, 0x911d, "%s app auth fail\n", __func__);

        sg_copy_to_buffer(bsg_job->request_payload.sg_list,
            bsg_job->request_payload.sg_cnt, &appplogifail,
            sizeof(struct auth_complete_cmd));

        /* silent unaligned access warning */
        portid.b.domain = appplogifail.u.d_id.b.domain;
        portid.b.area   = appplogifail.u.d_id.b.area;
        portid.b.al_pa  = appplogifail.u.d_id.b.al_pa;

        /*
         * TODO: edif: app has failed this plogi. Inform driver to
         * take any action (if any).
         */
        switch (appplogifail.type) {
        case PL_TYPE_WWPN:
                fcport = qla2x00_find_fcport_by_wwpn(vha,
                    appplogifail.u.wwpn, 0);
                SET_DID_STATUS(bsg_reply->result, DID_OK);
                break;
        case PL_TYPE_DID:
                fcport = qla2x00_find_fcport_by_pid(vha, &portid);
                if (!fcport)
                        ql_dbg(ql_dbg_edif, vha, 0x911d,
                            "%s d_id lookup failed: %x\n", __func__,
                            portid.b24);
                SET_DID_STATUS(bsg_reply->result, DID_OK);
                break;
        default:
                ql_dbg(ql_dbg_edif, vha, 0x911e,
                    "%s undefined type: %x\n", __func__,
                    appplogifail.type);
                bsg_job->reply_len = sizeof(struct fc_bsg_reply);
                SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                rval = -1;
                break;
        }

        ql_dbg(ql_dbg_edif, vha, 0x911d,
            "%s fcport is 0x%p\n", __func__, fcport);

        if (fcport) {
                /* set/reset edif values and flags */
                ql_dbg(ql_dbg_edif, vha, 0x911e,
                    "%s reset the auth process - %8phC, loopid=%x portid=%06x.\n",
                    __func__, fcport->port_name, fcport->loop_id, fcport->d_id.b24);

                if (qla_ini_mode_enabled(fcport->vha)) {
                        fcport->send_els_logo = 1;
                        qlt_schedule_sess_for_deletion(fcport);
                }
        }

        return rval;
}

/**
 * qla_edif_app_getfcinfo - app would like to read session info (wwpn, nportid,
 *   [initiator|target] mode.  It can specific session with specific nport id or
 *   all sessions.
 * @vha: host adapter pointer
 * @bsg_job: user request pointer
 */
static int
qla_edif_app_getfcinfo(scsi_qla_host_t *vha, struct bsg_job *bsg_job)
{
        int32_t                 rval = 0;
        int32_t                 pcnt = 0;
        struct fc_bsg_reply     *bsg_reply = bsg_job->reply;
        struct app_pinfo_req    app_req;
        struct app_pinfo_reply  *app_reply;
        port_id_t               tdid;

        ql_dbg(ql_dbg_edif, vha, 0x911d, "%s app get fcinfo\n", __func__);

        sg_copy_to_buffer(bsg_job->request_payload.sg_list,
            bsg_job->request_payload.sg_cnt, &app_req,
            sizeof(struct app_pinfo_req));

        app_reply = kzalloc((sizeof(struct app_pinfo_reply) +
            sizeof(struct app_pinfo) * app_req.num_ports), GFP_KERNEL);

        if (!app_reply) {
                SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                rval = -1;
        } else {
                struct fc_port  *fcport = NULL, *tf;

                app_reply->version = EDIF_VERSION1;

                list_for_each_entry_safe(fcport, tf, &vha->vp_fcports, list) {
                        if (!(fcport->flags & FCF_FCSP_DEVICE))
                                continue;

                        tdid.b.domain = app_req.remote_pid.domain;
                        tdid.b.area = app_req.remote_pid.area;
                        tdid.b.al_pa = app_req.remote_pid.al_pa;

                        ql_dbg(ql_dbg_edif, vha, 0x2058,
                            "APP request entry - portid=%06x.\n", tdid.b24);

                        /* Ran out of space */
                        if (pcnt >= app_req.num_ports)
                                break;

                        if (tdid.b24 != 0 && tdid.b24 != fcport->d_id.b24)
                                continue;

                        if (!N2N_TOPO(vha->hw)) {
                                if (fcport->scan_state != QLA_FCPORT_FOUND)
                                        continue;

                                if (fcport->port_type == FCT_UNKNOWN &&
                                    !fcport->fc4_features)
                                        rval = qla24xx_async_gffid(vha, fcport,
                                                                   true);

                                if (!rval &&
                                    !(fcport->fc4_features & FC4_FF_TARGET ||
                                      fcport->port_type &
                                      (FCT_TARGET | FCT_NVME_TARGET)))
                                        continue;
                        }

                        rval = 0;

                        app_reply->ports[pcnt].version = EDIF_VERSION1;
                        app_reply->ports[pcnt].remote_type =
                                VND_CMD_RTYPE_UNKNOWN;
                        if (fcport->port_type & (FCT_NVME_TARGET | FCT_TARGET))
                                app_reply->ports[pcnt].remote_type |=
                                        VND_CMD_RTYPE_TARGET;
                        if (fcport->port_type & (FCT_NVME_INITIATOR | FCT_INITIATOR))
                                app_reply->ports[pcnt].remote_type |=
                                        VND_CMD_RTYPE_INITIATOR;

                        app_reply->ports[pcnt].remote_pid = fcport->d_id;

                        ql_dbg(ql_dbg_edif, vha, 0x2058,
                            "Found FC_SP fcport - nn %8phN pn %8phN pcnt %d portid=%06x secure %d.\n",
                            fcport->node_name, fcport->port_name, pcnt,
                            fcport->d_id.b24, fcport->flags & FCF_FCSP_DEVICE);

                        switch (fcport->edif.auth_state) {
                        case VND_CMD_AUTH_STATE_ELS_RCVD:
                                if (fcport->disc_state == DSC_LOGIN_AUTH_PEND) {
                                        fcport->edif.auth_state = VND_CMD_AUTH_STATE_NEEDED;
                                        app_reply->ports[pcnt].auth_state =
                                                VND_CMD_AUTH_STATE_NEEDED;
                                } else {
                                        app_reply->ports[pcnt].auth_state =
                                                VND_CMD_AUTH_STATE_ELS_RCVD;
                                }
                                break;
                        default:
                                app_reply->ports[pcnt].auth_state = fcport->edif.auth_state;
                                break;
                        }

                        memcpy(app_reply->ports[pcnt].remote_wwpn,
                            fcport->port_name, 8);

                        app_reply->ports[pcnt].remote_state =
                                (atomic_read(&fcport->state) ==
                                    FCS_ONLINE ? 1 : 0);

                        pcnt++;

                        if (tdid.b24 != 0)
                                break;
                }
                app_reply->port_count = pcnt;
                SET_DID_STATUS(bsg_reply->result, DID_OK);
        }

        bsg_job->reply_len = sizeof(struct fc_bsg_reply);
        bsg_reply->reply_payload_rcv_len = sg_copy_from_buffer(bsg_job->reply_payload.sg_list,
                                                               bsg_job->reply_payload.sg_cnt,
                                                               app_reply,
                                                               sizeof(struct app_pinfo_reply) + sizeof(struct app_pinfo) * pcnt);

        kfree(app_reply);

        return rval;
}

/**
 * qla_edif_app_getstats - app would like to read various statistics info
 * @vha: host adapter pointer
 * @bsg_job: user request
 */
static int32_t
qla_edif_app_getstats(scsi_qla_host_t *vha, struct bsg_job *bsg_job)
{
        int32_t                 rval = 0;
        struct fc_bsg_reply     *bsg_reply = bsg_job->reply;
        uint32_t size;

        struct app_sinfo_req    app_req;
        struct app_stats_reply  *app_reply;
        uint32_t pcnt = 0;

        sg_copy_to_buffer(bsg_job->request_payload.sg_list,
            bsg_job->request_payload.sg_cnt, &app_req,
            sizeof(struct app_sinfo_req));
        if (app_req.num_ports == 0) {
                ql_dbg(ql_dbg_async, vha, 0x911d,
                   "%s app did not indicate number of ports to return\n",
                    __func__);
                SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                rval = -1;
        }

        size = sizeof(struct app_stats_reply) +
            (sizeof(struct app_sinfo) * app_req.num_ports);

        app_reply = kzalloc(size, GFP_KERNEL);
        if (!app_reply) {
                SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                rval = -1;
        } else {
                struct fc_port  *fcport = NULL, *tf;

                app_reply->version = EDIF_VERSION1;

                list_for_each_entry_safe(fcport, tf, &vha->vp_fcports, list) {
                        if (fcport->edif.enable) {
                                if (pcnt >= app_req.num_ports)
                                        break;

                                app_reply->elem[pcnt].rekey_count =
                                    fcport->edif.rekey_cnt;
                                app_reply->elem[pcnt].tx_bytes =
                                    fcport->edif.tx_bytes;
                                app_reply->elem[pcnt].rx_bytes =
                                    fcport->edif.rx_bytes;

                                memcpy(app_reply->elem[pcnt].remote_wwpn,
                                    fcport->port_name, 8);

                                pcnt++;
                        }
                }
                app_reply->elem_count = pcnt;
                SET_DID_STATUS(bsg_reply->result, DID_OK);
        }

        bsg_job->reply_len = sizeof(struct fc_bsg_reply);
        bsg_reply->reply_payload_rcv_len =
            sg_copy_from_buffer(bsg_job->reply_payload.sg_list,
               bsg_job->reply_payload.sg_cnt, app_reply,
               sizeof(struct app_stats_reply) + (sizeof(struct app_sinfo) * pcnt));

        kfree(app_reply);

        return rval;
}

static int32_t
qla_edif_ack(scsi_qla_host_t *vha, struct bsg_job *bsg_job)
{
        struct fc_port *fcport;
        struct aen_complete_cmd ack;
        struct fc_bsg_reply     *bsg_reply = bsg_job->reply;

        sg_copy_to_buffer(bsg_job->request_payload.sg_list,
                          bsg_job->request_payload.sg_cnt, &ack, sizeof(ack));

        ql_dbg(ql_dbg_edif, vha, 0x70cf,
               "%s: %06x event_code %x\n",
               __func__, ack.port_id.b24, ack.event_code);

        fcport = qla2x00_find_fcport_by_pid(vha, &ack.port_id);
        SET_DID_STATUS(bsg_reply->result, DID_OK);

        if (!fcport) {
                ql_dbg(ql_dbg_edif, vha, 0x70cf,
                       "%s: unable to find fcport %06x \n",
                       __func__, ack.port_id.b24);
                return 0;
        }

        switch (ack.event_code) {
        case VND_CMD_AUTH_STATE_SESSION_SHUTDOWN:
                fcport->edif.sess_down_acked = 1;
                break;
        default:
                break;
        }
        return 0;
}

static int qla_edif_consume_dbell(scsi_qla_host_t *vha, struct bsg_job *bsg_job)
{
        struct fc_bsg_reply     *bsg_reply = bsg_job->reply;
        u32 sg_skip, reply_payload_len;
        bool keep;
        struct edb_node *dbnode = NULL;
        struct edif_app_dbell ap;
        int dat_size = 0;

        sg_skip = 0;
        reply_payload_len = bsg_job->reply_payload.payload_len;

        while ((reply_payload_len - sg_skip) >= sizeof(struct edb_node)) {
                dbnode = qla_edb_getnext(vha);
                if (dbnode) {
                        keep = true;
                        dat_size = 0;
                        ap.event_code = dbnode->ntype;
                        switch (dbnode->ntype) {
                        case VND_CMD_AUTH_STATE_SESSION_SHUTDOWN:
                        case VND_CMD_AUTH_STATE_NEEDED:
                                ap.port_id = dbnode->u.plogi_did;
                                dat_size += sizeof(ap.port_id);
                                break;
                        case VND_CMD_AUTH_STATE_ELS_RCVD:
                                ap.port_id = dbnode->u.els_sid;
                                dat_size += sizeof(ap.port_id);
                                break;
                        case VND_CMD_AUTH_STATE_SAUPDATE_COMPL:
                                ap.port_id = dbnode->u.sa_aen.port_id;
                                memcpy(&ap.event_data, &dbnode->u,
                                    sizeof(struct edif_sa_update_aen));
                                dat_size += sizeof(struct edif_sa_update_aen);
                                break;
                        default:
                                keep = false;
                                ql_log(ql_log_warn, vha, 0x09102,
                                        "%s unknown DB type=%d %p\n",
                                        __func__, dbnode->ntype, dbnode);
                                break;
                        }
                        ap.event_data_size = dat_size;
                        /* 8 = sizeof(ap.event_code + ap.event_data_size) */
                        dat_size += 8;
                        if (keep)
                                sg_skip += sg_copy_buffer(bsg_job->reply_payload.sg_list,
                                                bsg_job->reply_payload.sg_cnt,
                                                &ap, dat_size, sg_skip, false);

                        ql_dbg(ql_dbg_edif, vha, 0x09102,
                                "%s Doorbell consumed : type=%d %p\n",
                                __func__, dbnode->ntype, dbnode);

                        kfree(dbnode);
                } else {
                        break;
                }
        }

        SET_DID_STATUS(bsg_reply->result, DID_OK);
        bsg_reply->reply_payload_rcv_len = sg_skip;
        bsg_job->reply_len = sizeof(struct fc_bsg_reply);

        return 0;
}

static void __qla_edif_dbell_bsg_done(scsi_qla_host_t *vha, struct bsg_job *bsg_job,
        u32 delay)
{
        struct fc_bsg_reply *bsg_reply = bsg_job->reply;

        /* small sleep for doorbell events to accumulate */
        if (delay)
                msleep(delay);

        qla_edif_consume_dbell(vha, bsg_job);

        bsg_job_done(bsg_job, bsg_reply->result, bsg_reply->reply_payload_rcv_len);
}

static void qla_edif_dbell_bsg_done(scsi_qla_host_t *vha)
{
        unsigned long flags;
        struct bsg_job *prev_bsg_job = NULL;

        spin_lock_irqsave(&vha->e_dbell.db_lock, flags);
        if (vha->e_dbell.dbell_bsg_job) {
                prev_bsg_job = vha->e_dbell.dbell_bsg_job;
                vha->e_dbell.dbell_bsg_job = NULL;
        }
        spin_unlock_irqrestore(&vha->e_dbell.db_lock, flags);

        if (prev_bsg_job)
                __qla_edif_dbell_bsg_done(vha, prev_bsg_job, 0);
}

static int
qla_edif_dbell_bsg(scsi_qla_host_t *vha, struct bsg_job *bsg_job)
{
        unsigned long flags;
        bool return_bsg = false;

        /* flush previous dbell bsg */
        qla_edif_dbell_bsg_done(vha);

        spin_lock_irqsave(&vha->e_dbell.db_lock, flags);
        if (list_empty(&vha->e_dbell.head) && DBELL_ACTIVE(vha)) {
                /*
                 * when the next db event happens, bsg_job will return.
                 * Otherwise, timer will return it.
                 */
                vha->e_dbell.dbell_bsg_job = bsg_job;
                vha->e_dbell.bsg_expire = jiffies + 10 * HZ;
        } else {
                return_bsg = true;
        }
        spin_unlock_irqrestore(&vha->e_dbell.db_lock, flags);

        if (return_bsg)
                __qla_edif_dbell_bsg_done(vha, bsg_job, 1);

        return 0;
}

int32_t
qla_edif_app_mgmt(struct bsg_job *bsg_job)
{
        struct fc_bsg_request   *bsg_request = bsg_job->request;
        struct fc_bsg_reply     *bsg_reply = bsg_job->reply;
        struct Scsi_Host *host = fc_bsg_to_shost(bsg_job);
        scsi_qla_host_t         *vha = shost_priv(host);
        struct app_id           appcheck;
        bool done = true;
        int32_t         rval = 0;
        uint32_t        vnd_sc = bsg_request->rqst_data.h_vendor.vendor_cmd[1];
        u32 level = ql_dbg_edif;

        /* doorbell is high traffic */
        if (vnd_sc == QL_VND_SC_READ_DBELL)
                level = 0;

        ql_dbg(level, vha, 0x911d, "%s vnd subcmd=%x\n",
            __func__, vnd_sc);

        sg_copy_to_buffer(bsg_job->request_payload.sg_list,
            bsg_job->request_payload.sg_cnt, &appcheck,
            sizeof(struct app_id));

        if (!vha->hw->flags.edif_enabled ||
                test_bit(VPORT_DELETE, &vha->dpc_flags)) {
                ql_dbg(level, vha, 0x911d,
                    "%s edif not enabled or vp delete. bsg ptr done %p. dpc_flags %lx\n",
                    __func__, bsg_job, vha->dpc_flags);

                SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                goto done;
        }

        if (!qla_edif_app_check(vha, appcheck)) {
                ql_dbg(level, vha, 0x911d,
                    "%s app checked failed.\n",
                    __func__);

                bsg_job->reply_len = sizeof(struct fc_bsg_reply);
                SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                goto done;
        }

        switch (vnd_sc) {
        case QL_VND_SC_SA_UPDATE:
                done = false;
                rval = qla24xx_sadb_update(bsg_job);
                break;
        case QL_VND_SC_APP_START:
                rval = qla_edif_app_start(vha, bsg_job);
                break;
        case QL_VND_SC_APP_STOP:
                rval = qla_edif_app_stop(vha, bsg_job);
                break;
        case QL_VND_SC_AUTH_OK:
                rval = qla_edif_app_authok(vha, bsg_job);
                break;
        case QL_VND_SC_AUTH_FAIL:
                rval = qla_edif_app_authfail(vha, bsg_job);
                break;
        case QL_VND_SC_GET_FCINFO:
                rval = qla_edif_app_getfcinfo(vha, bsg_job);
                break;
        case QL_VND_SC_GET_STATS:
                rval = qla_edif_app_getstats(vha, bsg_job);
                break;
        case QL_VND_SC_AEN_COMPLETE:
                rval = qla_edif_ack(vha, bsg_job);
                break;
        case QL_VND_SC_READ_DBELL:
                rval = qla_edif_dbell_bsg(vha, bsg_job);
                done = false;
                break;
        default:
                ql_dbg(ql_dbg_edif, vha, 0x911d, "%s unknown cmd=%x\n",
                    __func__,
                    bsg_request->rqst_data.h_vendor.vendor_cmd[1]);
                rval = EXT_STATUS_INVALID_PARAM;
                done = false;
                break;
        }

done:
        if (done) {
                ql_dbg(level, vha, 0x7009,
                    "%s: %d  bsg ptr done %p\n", __func__, __LINE__, bsg_job);
                bsg_job_done(bsg_job, bsg_reply->result,
                    bsg_reply->reply_payload_rcv_len);
        }

        return rval;
}

static struct edif_sa_ctl *
qla_edif_add_sa_ctl(fc_port_t *fcport, struct qla_sa_update_frame *sa_frame,
        int dir)
{
        struct  edif_sa_ctl *sa_ctl;
        struct qla_sa_update_frame *sap;
        int     index = sa_frame->fast_sa_index;
        unsigned long flags = 0;

        sa_ctl = kzalloc_obj(*sa_ctl);
        if (!sa_ctl) {
                /* couldn't get space */
                ql_dbg(ql_dbg_edif, fcport->vha, 0x9100,
                    "unable to allocate SA CTL\n");
                return NULL;
        }

        /*
         * need to allocate sa_index here and save it
         * in both sa_ctl->index and sa_frame->fast_sa_index;
         * If alloc fails then delete sa_ctl and return NULL
         */
        INIT_LIST_HEAD(&sa_ctl->next);
        sap = &sa_ctl->sa_frame;
        *sap = *sa_frame;
        sa_ctl->index = index;
        sa_ctl->fcport = fcport;
        sa_ctl->flags = 0;
        sa_ctl->state = 0L;
        ql_dbg(ql_dbg_edif, fcport->vha, 0x9100,
            "%s: Added sa_ctl %p, index %d, state 0x%lx\n",
            __func__, sa_ctl, sa_ctl->index, sa_ctl->state);
        spin_lock_irqsave(&fcport->edif.sa_list_lock, flags);
        if (dir == SAU_FLG_TX)
                list_add_tail(&sa_ctl->next, &fcport->edif.tx_sa_list);
        else
                list_add_tail(&sa_ctl->next, &fcport->edif.rx_sa_list);
        spin_unlock_irqrestore(&fcport->edif.sa_list_lock, flags);

        return sa_ctl;
}

void
qla_edif_flush_sa_ctl_lists(fc_port_t *fcport)
{
        struct edif_sa_ctl *sa_ctl, *tsa_ctl;
        unsigned long flags = 0;

        spin_lock_irqsave(&fcport->edif.sa_list_lock, flags);

        list_for_each_entry_safe(sa_ctl, tsa_ctl, &fcport->edif.tx_sa_list,
            next) {
                list_del(&sa_ctl->next);
                kfree(sa_ctl);
        }

        list_for_each_entry_safe(sa_ctl, tsa_ctl, &fcport->edif.rx_sa_list,
            next) {
                list_del(&sa_ctl->next);
                kfree(sa_ctl);
        }

        spin_unlock_irqrestore(&fcport->edif.sa_list_lock, flags);
}

struct edif_sa_ctl *
qla_edif_find_sa_ctl_by_index(fc_port_t *fcport, int index, int dir)
{
        struct edif_sa_ctl *sa_ctl, *tsa_ctl;
        struct list_head *sa_list;

        if (dir == SAU_FLG_TX)
                sa_list = &fcport->edif.tx_sa_list;
        else
                sa_list = &fcport->edif.rx_sa_list;

        list_for_each_entry_safe(sa_ctl, tsa_ctl, sa_list, next) {
                if (test_bit(EDIF_SA_CTL_USED, &sa_ctl->state) &&
                    sa_ctl->index == index)
                        return sa_ctl;
        }
        return NULL;
}

/* add the sa to the correct list */
static int
qla24xx_check_sadb_avail_slot(struct bsg_job *bsg_job, fc_port_t *fcport,
        struct qla_sa_update_frame *sa_frame)
{
        struct edif_sa_ctl *sa_ctl = NULL;
        int dir;
        uint16_t sa_index;

        dir = (sa_frame->flags & SAU_FLG_TX);

        /* map the spi to an sa_index */
        sa_index = qla_edif_sadb_get_sa_index(fcport, sa_frame);
        if (sa_index == RX_DELETE_NO_EDIF_SA_INDEX) {
                /* process rx delete */
                ql_dbg(ql_dbg_edif, fcport->vha, 0x3063,
                    "%s: rx delete for lid 0x%x, spi 0x%x, no entry found\n",
                    __func__, fcport->loop_id, sa_frame->spi);

                /* build and send the aen */
                fcport->edif.rx_sa_set = 1;
                fcport->edif.rx_sa_pending = 0;
                qla_edb_eventcreate(fcport->vha,
                    VND_CMD_AUTH_STATE_SAUPDATE_COMPL,
                    QL_VND_SA_STAT_SUCCESS,
                    QL_VND_RX_SA_KEY, fcport);

                /* force a return of good bsg status; */
                return RX_DELETE_NO_EDIF_SA_INDEX;
        } else if (sa_index == INVALID_EDIF_SA_INDEX) {
                ql_dbg(ql_dbg_edif, fcport->vha, 0x9100,
                    "%s: Failed to get sa_index for spi 0x%x, dir: %d\n",
                    __func__, sa_frame->spi, dir);
                return INVALID_EDIF_SA_INDEX;
        }

        ql_dbg(ql_dbg_edif, fcport->vha, 0x9100,
            "%s: index %d allocated to spi 0x%x, dir: %d, nport_handle: 0x%x\n",
            __func__, sa_index, sa_frame->spi, dir, fcport->loop_id);

        /* This is a local copy of sa_frame. */
        sa_frame->fast_sa_index = sa_index;
        /* create the sa_ctl */
        sa_ctl = qla_edif_add_sa_ctl(fcport, sa_frame, dir);
        if (!sa_ctl) {
                ql_dbg(ql_dbg_edif, fcport->vha, 0x9100,
                    "%s: Failed to add sa_ctl for spi 0x%x, dir: %d, sa_index: %d\n",
                    __func__, sa_frame->spi, dir, sa_index);
                return -1;
        }

        set_bit(EDIF_SA_CTL_USED, &sa_ctl->state);

        if (dir == SAU_FLG_TX)
                fcport->edif.tx_rekey_cnt++;
        else
                fcport->edif.rx_rekey_cnt++;

        ql_dbg(ql_dbg_edif, fcport->vha, 0x9100,
            "%s: Found sa_ctl %p, index %d, state 0x%lx, tx_cnt %d, rx_cnt %d, nport_handle: 0x%x\n",
            __func__, sa_ctl, sa_ctl->index, sa_ctl->state,
            fcport->edif.tx_rekey_cnt,
            fcport->edif.rx_rekey_cnt, fcport->loop_id);

        return 0;
}

#define QLA_SA_UPDATE_FLAGS_RX_KEY      0x0
#define QLA_SA_UPDATE_FLAGS_TX_KEY      0x2
#define EDIF_MSLEEP_INTERVAL 100
#define EDIF_RETRY_COUNT  50

int
qla24xx_sadb_update(struct bsg_job *bsg_job)
{
        struct  fc_bsg_reply    *bsg_reply = bsg_job->reply;
        struct Scsi_Host *host = fc_bsg_to_shost(bsg_job);
        scsi_qla_host_t *vha = shost_priv(host);
        fc_port_t               *fcport = NULL;
        srb_t                   *sp = NULL;
        struct edif_list_entry *edif_entry = NULL;
        int                     found = 0;
        int                     rval = 0;
        int result = 0, cnt;
        struct qla_sa_update_frame sa_frame;
        struct srb_iocb *iocb_cmd;
        port_id_t portid;

        ql_dbg(ql_dbg_edif + ql_dbg_verbose, vha, 0x911d,
            "%s entered, vha: 0x%p\n", __func__, vha);

        sg_copy_to_buffer(bsg_job->request_payload.sg_list,
            bsg_job->request_payload.sg_cnt, &sa_frame,
            sizeof(struct qla_sa_update_frame));

        /* Check if host is online */
        if (!vha->flags.online) {
                ql_log(ql_log_warn, vha, 0x70a1, "Host is not online\n");
                rval = -EIO;
                SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                goto done;
        }

        if (DBELL_INACTIVE(vha)) {
                ql_log(ql_log_warn, vha, 0x70a1, "App not started\n");
                rval = -EIO;
                SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                goto done;
        }

        /* silent unaligned access warning */
        portid.b.domain = sa_frame.port_id.b.domain;
        portid.b.area   = sa_frame.port_id.b.area;
        portid.b.al_pa  = sa_frame.port_id.b.al_pa;

        fcport = qla2x00_find_fcport_by_pid(vha, &portid);
        if (fcport) {
                found = 1;
                if (sa_frame.flags == QLA_SA_UPDATE_FLAGS_TX_KEY)
                        fcport->edif.tx_bytes = 0;
                if (sa_frame.flags == QLA_SA_UPDATE_FLAGS_RX_KEY)
                        fcport->edif.rx_bytes = 0;
        }

        if (!found) {
                ql_dbg(ql_dbg_edif, vha, 0x70a3, "Failed to find port= %06x\n",
                    sa_frame.port_id.b24);
                rval = -EINVAL;
                SET_DID_STATUS(bsg_reply->result, DID_NO_CONNECT);
                goto done;
        }

        /* make sure the nport_handle is valid */
        if (fcport->loop_id == FC_NO_LOOP_ID) {
                ql_dbg(ql_dbg_edif, vha, 0x70e1,
                    "%s: %8phN lid=FC_NO_LOOP_ID, spi: 0x%x, DS %d, returning NO_CONNECT\n",
                    __func__, fcport->port_name, sa_frame.spi,
                    fcport->disc_state);
                rval = -EINVAL;
                SET_DID_STATUS(bsg_reply->result, DID_NO_CONNECT);
                goto done;
        }

        /* allocate and queue an sa_ctl */
        result = qla24xx_check_sadb_avail_slot(bsg_job, fcport, &sa_frame);

        /* failure of bsg */
        if (result == INVALID_EDIF_SA_INDEX) {
                ql_dbg(ql_dbg_edif, vha, 0x70e1,
                    "%s: %8phN, skipping update.\n",
                    __func__, fcport->port_name);
                rval = -EINVAL;
                SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                goto done;

        /* rx delete failure */
        } else if (result == RX_DELETE_NO_EDIF_SA_INDEX) {
                ql_dbg(ql_dbg_edif, vha, 0x70e1,
                    "%s: %8phN, skipping rx delete.\n",
                    __func__, fcport->port_name);
                SET_DID_STATUS(bsg_reply->result, DID_OK);
                goto done;
        }

        ql_dbg(ql_dbg_edif, vha, 0x70e1,
            "%s: %8phN, sa_index in sa_frame: %d flags %xh\n",
            __func__, fcport->port_name, sa_frame.fast_sa_index,
            sa_frame.flags);

        /* looking for rx index and delete */
        if (((sa_frame.flags & SAU_FLG_TX) == 0) &&
            (sa_frame.flags & SAU_FLG_INV)) {
                uint16_t nport_handle = fcport->loop_id;
                uint16_t sa_index = sa_frame.fast_sa_index;

                /*
                 * make sure we have an existing rx key, otherwise just process
                 * this as a straight delete just like TX
                 * This is NOT a normal case, it indicates an error recovery or key cleanup
                 * by the ipsec code above us.
                 */
                edif_entry = qla_edif_list_find_sa_index(fcport, fcport->loop_id);
                if (!edif_entry) {
                        ql_dbg(ql_dbg_edif, vha, 0x911d,
                            "%s: WARNING: no active sa_index for nport_handle 0x%x, forcing delete for sa_index 0x%x\n",
                            __func__, fcport->loop_id, sa_index);
                        goto force_rx_delete;
                }

                /*
                 * if we have a forced delete for rx, remove the sa_index from the edif list
                 * and proceed with normal delete.  The rx delay timer should not be running
                 */
                if ((sa_frame.flags & SAU_FLG_FORCE_DELETE) == SAU_FLG_FORCE_DELETE) {
                        qla_edif_list_delete_sa_index(fcport, edif_entry);
                        ql_dbg(ql_dbg_edif, vha, 0x911d,
                            "%s: FORCE DELETE flag found for nport_handle 0x%x, sa_index 0x%x, forcing DELETE\n",
                            __func__, fcport->loop_id, sa_index);
                        kfree(edif_entry);
                        goto force_rx_delete;
                }

                /*
                 * delayed rx delete
                 *
                 * if delete_sa_index is not invalid then there is already
                 * a delayed index in progress, return bsg bad status
                 */
                if (edif_entry->delete_sa_index != INVALID_EDIF_SA_INDEX) {
                        struct edif_sa_ctl *sa_ctl;

                        ql_dbg(ql_dbg_edif, vha, 0x911d,
                            "%s: delete for lid 0x%x, delete_sa_index %d is pending\n",
                            __func__, edif_entry->handle, edif_entry->delete_sa_index);

                        /* free up the sa_ctl that was allocated with the sa_index */
                        sa_ctl = qla_edif_find_sa_ctl_by_index(fcport, sa_index,
                            (sa_frame.flags & SAU_FLG_TX));
                        if (sa_ctl) {
                                ql_dbg(ql_dbg_edif, vha, 0x3063,
                                    "%s: freeing sa_ctl for index %d\n",
                                    __func__, sa_ctl->index);
                                qla_edif_free_sa_ctl(fcport, sa_ctl, sa_ctl->index);
                        }

                        /* release the sa_index */
                        ql_dbg(ql_dbg_edif, vha, 0x3063,
                            "%s: freeing sa_index %d, nph: 0x%x\n",
                            __func__, sa_index, nport_handle);
                        qla_edif_sadb_delete_sa_index(fcport, nport_handle, sa_index);

                        rval = -EINVAL;
                        SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                        goto done;
                }

                fcport->edif.rekey_cnt++;

                /* configure and start the rx delay timer */
                edif_entry->fcport = fcport;
                edif_entry->timer.expires = jiffies + RX_DELAY_DELETE_TIMEOUT * HZ;

                ql_dbg(ql_dbg_edif, vha, 0x911d,
                    "%s: adding timer, entry: %p, delete sa_index %d, lid 0x%x to edif_list\n",
                    __func__, edif_entry, sa_index, nport_handle);

                /*
                 * Start the timer when we queue the delayed rx delete.
                 * This is an activity timer that goes off if we have not
                 * received packets with the new sa_index
                 */
                add_timer(&edif_entry->timer);

                /*
                 * sa_delete for rx key with an active rx key including this one
                 * add the delete rx sa index to the hash so we can look for it
                 * in the rsp queue.  Do this after making any changes to the
                 * edif_entry as part of the rx delete.
                 */

                ql_dbg(ql_dbg_edif, vha, 0x911d,
                    "%s: delete sa_index %d, lid 0x%x to edif_list. bsg done ptr %p\n",
                    __func__, sa_index, nport_handle, bsg_job);

                edif_entry->delete_sa_index = sa_index;

                bsg_job->reply_len = sizeof(struct fc_bsg_reply);
                bsg_reply->result = DID_OK << 16;

                goto done;

        /*
         * rx index and update
         * add the index to the list and continue with normal update
         */
        } else if (((sa_frame.flags & SAU_FLG_TX) == 0) &&
            ((sa_frame.flags & SAU_FLG_INV) == 0)) {
                /* sa_update for rx key */
                uint32_t nport_handle = fcport->loop_id;
                uint16_t sa_index = sa_frame.fast_sa_index;
                int result;

                /*
                 * add the update rx sa index to the hash so we can look for it
                 * in the rsp queue and continue normally
                 */

                ql_dbg(ql_dbg_edif, vha, 0x911d,
                    "%s:  adding update sa_index %d, lid 0x%x to edif_list\n",
                    __func__, sa_index, nport_handle);

                result = qla_edif_list_add_sa_update_index(fcport, sa_index,
                    nport_handle);
                if (result) {
                        ql_dbg(ql_dbg_edif, vha, 0x911d,
                            "%s: SA_UPDATE failed to add new sa index %d to list for lid 0x%x\n",
                            __func__, sa_index, nport_handle);
                }
        }
        if (sa_frame.flags & SAU_FLG_GMAC_MODE)
                fcport->edif.aes_gmac = 1;
        else
                fcport->edif.aes_gmac = 0;

force_rx_delete:
        /*
         * sa_update for both rx and tx keys, sa_delete for tx key
         * immediately process the request
         */
        sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL);
        if (!sp) {
                rval = -ENOMEM;
                SET_DID_STATUS(bsg_reply->result, DID_IMM_RETRY);
                goto done;
        }

        sp->type = SRB_SA_UPDATE;
        sp->name = "bsg_sa_update";
        sp->u.bsg_job = bsg_job;
        /* sp->free = qla2x00_bsg_sp_free; */
        sp->free = qla2x00_rel_sp;
        sp->done = qla2x00_bsg_job_done;
        iocb_cmd = &sp->u.iocb_cmd;
        iocb_cmd->u.sa_update.sa_frame  = sa_frame;
        cnt = 0;
retry:
        rval = qla2x00_start_sp(sp);
        switch (rval) {
        case QLA_SUCCESS:
                break;
        case -EAGAIN:
                msleep(EDIF_MSLEEP_INTERVAL);
                cnt++;
                if (cnt < EDIF_RETRY_COUNT)
                        goto retry;

                fallthrough;
        default:
                ql_log(ql_dbg_edif, vha, 0x70e3,
                       "%s qla2x00_start_sp failed=%d.\n",
                       __func__, rval);

                qla2x00_rel_sp(sp);
                rval = -EIO;
                SET_DID_STATUS(bsg_reply->result, DID_IMM_RETRY);
                goto done;
        }

        ql_dbg(ql_dbg_edif, vha, 0x911d,
            "%s:  %s sent, hdl=%x, portid=%06x.\n",
            __func__, sp->name, sp->handle, fcport->d_id.b24);

        fcport->edif.rekey_cnt++;
        bsg_job->reply_len = sizeof(struct fc_bsg_reply);
        SET_DID_STATUS(bsg_reply->result, DID_OK);

        return 0;

/*
 * send back error status
 */
done:
        bsg_job->reply_len = sizeof(struct fc_bsg_reply);
        ql_dbg(ql_dbg_edif, vha, 0x911d,
            "%s:status: FAIL, result: 0x%x, bsg ptr done %p\n",
            __func__, bsg_reply->result, bsg_job);
        bsg_job_done(bsg_job, bsg_reply->result,
            bsg_reply->reply_payload_rcv_len);

        return 0;
}

static void
qla_enode_free(scsi_qla_host_t *vha, struct enode *node)
{
        node->ntype = N_UNDEF;
        kfree(node);
}

/**
 * qla_enode_init - initialize enode structs & lock
 * @vha: host adapter pointer
 *
 * should only be called when driver attaching
 */
void
qla_enode_init(scsi_qla_host_t *vha)
{
        struct  qla_hw_data *ha = vha->hw;
        char    name[32];

        if (vha->pur_cinfo.enode_flags == ENODE_ACTIVE) {
                /* list still active - error */
                ql_dbg(ql_dbg_edif, vha, 0x09102, "%s enode still active\n",
                    __func__);
                return;
        }

        /* initialize lock which protects pur_core & init list */
        spin_lock_init(&vha->pur_cinfo.pur_lock);
        INIT_LIST_HEAD(&vha->pur_cinfo.head);

        snprintf(name, sizeof(name), "%s_%d_purex", QLA2XXX_DRIVER_NAME,
            ha->pdev->device);
}

/**
 * qla_enode_stop - stop and clear and enode data
 * @vha: host adapter pointer
 *
 * called when app notified it is exiting
 */
void
qla_enode_stop(scsi_qla_host_t *vha)
{
        unsigned long flags;
        struct enode *node, *q;

        if (vha->pur_cinfo.enode_flags != ENODE_ACTIVE) {
                /* doorbell list not enabled */
                ql_dbg(ql_dbg_edif, vha, 0x09102,
                    "%s enode not active\n", __func__);
                return;
        }

        /* grab lock so list doesn't move */
        spin_lock_irqsave(&vha->pur_cinfo.pur_lock, flags);

        vha->pur_cinfo.enode_flags &= ~ENODE_ACTIVE; /* mark it not active */

        /* hopefully this is a null list at this point */
        list_for_each_entry_safe(node, q, &vha->pur_cinfo.head, list) {
                ql_dbg(ql_dbg_edif, vha, 0x910f,
                    "%s freeing enode type=%x, cnt=%x\n", __func__, node->ntype,
                    node->dinfo.nodecnt);
                list_del_init(&node->list);
                qla_enode_free(vha, node);
        }
        spin_unlock_irqrestore(&vha->pur_cinfo.pur_lock, flags);
}

static void qla_enode_clear(scsi_qla_host_t *vha, port_id_t portid)
{
        unsigned    long flags;
        struct enode    *e, *tmp;
        struct purexevent   *purex;
        LIST_HEAD(enode_list);

        if (vha->pur_cinfo.enode_flags != ENODE_ACTIVE) {
                ql_dbg(ql_dbg_edif, vha, 0x09102,
                       "%s enode not active\n", __func__);
                return;
        }
        spin_lock_irqsave(&vha->pur_cinfo.pur_lock, flags);
        list_for_each_entry_safe(e, tmp, &vha->pur_cinfo.head, list) {
                purex = &e->u.purexinfo;
                if (purex->pur_info.pur_sid.b24 == portid.b24) {
                        ql_dbg(ql_dbg_edif, vha, 0x911d,
                            "%s free ELS sid=%06x. xchg %x, nb=%xh\n",
                            __func__, portid.b24,
                            purex->pur_info.pur_rx_xchg_address,
                            purex->pur_info.pur_bytes_rcvd);

                        list_del_init(&e->list);
                        list_add_tail(&e->list, &enode_list);
                }
        }
        spin_unlock_irqrestore(&vha->pur_cinfo.pur_lock, flags);

        list_for_each_entry_safe(e, tmp, &enode_list, list) {
                list_del_init(&e->list);
                qla_enode_free(vha, e);
        }
}

/*
 *  allocate enode struct and populate buffer
 *  returns: enode pointer with buffers
 *           NULL on error
 */
static struct enode *
qla_enode_alloc(scsi_qla_host_t *vha, uint32_t ntype)
{
        struct enode            *node;
        struct purexevent       *purex;

        node = kzalloc(RX_ELS_SIZE, GFP_ATOMIC);
        if (!node)
                return NULL;

        purex = &node->u.purexinfo;
        purex->msgp = (u8 *)(node + 1);
        purex->msgp_len = ELS_MAX_PAYLOAD;

        node->ntype = ntype;
        INIT_LIST_HEAD(&node->list);
        return node;
}

static void
qla_enode_add(scsi_qla_host_t *vha, struct enode *ptr)
{
        unsigned long flags;

        ql_dbg(ql_dbg_edif + ql_dbg_verbose, vha, 0x9109,
            "%s add enode for type=%x, cnt=%x\n",
            __func__, ptr->ntype, ptr->dinfo.nodecnt);

        spin_lock_irqsave(&vha->pur_cinfo.pur_lock, flags);
        list_add_tail(&ptr->list, &vha->pur_cinfo.head);
        spin_unlock_irqrestore(&vha->pur_cinfo.pur_lock, flags);

        return;
}

static struct enode *
qla_enode_find(scsi_qla_host_t *vha, uint32_t ntype, uint32_t p1, uint32_t p2)
{
        struct enode            *node_rtn = NULL;
        struct enode            *list_node, *q;
        unsigned long           flags;
        uint32_t                sid;
        struct purexevent       *purex;

        /* secure the list from moving under us */
        spin_lock_irqsave(&vha->pur_cinfo.pur_lock, flags);

        list_for_each_entry_safe(list_node, q, &vha->pur_cinfo.head, list) {

                /* node type determines what p1 and p2 are */
                purex = &list_node->u.purexinfo;
                sid = p1;

                if (purex->pur_info.pur_sid.b24 == sid) {
                        /* found it and its complete */
                        node_rtn = list_node;
                        list_del(&list_node->list);
                        break;
                }
        }

        spin_unlock_irqrestore(&vha->pur_cinfo.pur_lock, flags);

        return node_rtn;
}

/**
 * qla_pur_get_pending - read/return authentication message sent
 *  from remote port
 * @vha: host adapter pointer
 * @fcport: session pointer
 * @bsg_job: user request where the message is copy to.
 */
static int
qla_pur_get_pending(scsi_qla_host_t *vha, fc_port_t *fcport,
        struct bsg_job *bsg_job)
{
        struct enode            *ptr;
        struct purexevent       *purex;
        struct qla_bsg_auth_els_reply *rpl =
            (struct qla_bsg_auth_els_reply *)bsg_job->reply;

        bsg_job->reply_len = sizeof(*rpl);

        ptr = qla_enode_find(vha, N_PUREX, fcport->d_id.b24, PUR_GET);
        if (!ptr) {
                ql_dbg(ql_dbg_edif, vha, 0x9111,
                    "%s no enode data found for %8phN sid=%06x\n",
                    __func__, fcport->port_name, fcport->d_id.b24);
                SET_DID_STATUS(rpl->r.result, DID_IMM_RETRY);
                return -EIO;
        }

        /*
         * enode is now off the linked list and is ours to deal with
         */
        purex = &ptr->u.purexinfo;

        /* Copy info back to caller */
        rpl->rx_xchg_address = purex->pur_info.pur_rx_xchg_address;

        SET_DID_STATUS(rpl->r.result, DID_OK);
        rpl->r.reply_payload_rcv_len =
            sg_pcopy_from_buffer(bsg_job->reply_payload.sg_list,
                bsg_job->reply_payload.sg_cnt, purex->msgp,
                purex->pur_info.pur_bytes_rcvd, 0);

        /* data copy / passback completed - destroy enode */
        qla_enode_free(vha, ptr);

        return 0;
}

/* it is assume qpair lock is held */
static int
qla_els_reject_iocb(scsi_qla_host_t *vha, struct qla_qpair *qp,
        struct qla_els_pt_arg *a)
{
        struct els_entry_24xx *els_iocb;

        els_iocb = __qla2x00_alloc_iocbs(qp, NULL);
        if (!els_iocb) {
                ql_log(ql_log_warn, vha, 0x700c,
                    "qla2x00_alloc_iocbs failed.\n");
                return QLA_FUNCTION_FAILED;
        }

        qla_els_pt_iocb(vha, els_iocb, a);

        ql_dbg(ql_dbg_edif, vha, 0x0183,
            "Sending ELS reject ox_id %04x s:%06x -> d:%06x\n",
            a->ox_id, a->sid.b24, a->did.b24);
        ql_dump_buffer(ql_dbg_edif + ql_dbg_verbose, vha, 0x0185,
            vha->hw->elsrej.c, sizeof(*vha->hw->elsrej.c));
        /* flush iocb to mem before notifying hw doorbell */
        wmb();
        qla2x00_start_iocbs(vha, qp->req);
        return 0;
}

void
qla_edb_init(scsi_qla_host_t *vha)
{
        if (DBELL_ACTIVE(vha)) {
                /* list already init'd - error */
                ql_dbg(ql_dbg_edif, vha, 0x09102,
                    "edif db already initialized, cannot reinit\n");
                return;
        }

        /* initialize lock which protects doorbell & init list */
        spin_lock_init(&vha->e_dbell.db_lock);
        INIT_LIST_HEAD(&vha->e_dbell.head);
}

static void qla_edb_clear(scsi_qla_host_t *vha, port_id_t portid)
{
        unsigned long flags;
        struct edb_node *e, *tmp;
        port_id_t sid;
        LIST_HEAD(edb_list);

        if (DBELL_INACTIVE(vha)) {
                /* doorbell list not enabled */
                ql_dbg(ql_dbg_edif, vha, 0x09102,
                       "%s doorbell not enabled\n", __func__);
                return;
        }

        /* grab lock so list doesn't move */
        spin_lock_irqsave(&vha->e_dbell.db_lock, flags);
        list_for_each_entry_safe(e, tmp, &vha->e_dbell.head, list) {
                switch (e->ntype) {
                case VND_CMD_AUTH_STATE_NEEDED:
                case VND_CMD_AUTH_STATE_SESSION_SHUTDOWN:
                        sid = e->u.plogi_did;
                        break;
                case VND_CMD_AUTH_STATE_ELS_RCVD:
                        sid = e->u.els_sid;
                        break;
                case VND_CMD_AUTH_STATE_SAUPDATE_COMPL:
                        /* app wants to see this  */
                        continue;
                default:
                        ql_log(ql_log_warn, vha, 0x09102,
                               "%s unknown node type: %x\n", __func__, e->ntype);
                        sid.b24 = 0;
                        break;
                }
                if (sid.b24 == portid.b24) {
                        ql_dbg(ql_dbg_edif, vha, 0x910f,
                               "%s free doorbell event : node type = %x %p\n",
                               __func__, e->ntype, e);
                        list_del_init(&e->list);
                        list_add_tail(&e->list, &edb_list);
                }
        }
        spin_unlock_irqrestore(&vha->e_dbell.db_lock, flags);

        list_for_each_entry_safe(e, tmp, &edb_list, list)
                qla_edb_node_free(vha, e);
}

/* function called when app is stopping */

void
qla_edb_stop(scsi_qla_host_t *vha)
{
        unsigned long flags;
        struct edb_node *node, *q;

        if (DBELL_INACTIVE(vha)) {
                /* doorbell list not enabled */
                ql_dbg(ql_dbg_edif, vha, 0x09102,
                    "%s doorbell not enabled\n", __func__);
                return;
        }

        /* grab lock so list doesn't move */
        spin_lock_irqsave(&vha->e_dbell.db_lock, flags);

        vha->e_dbell.db_flags &= ~EDB_ACTIVE; /* mark it not active */
        /* hopefully this is a null list at this point */
        list_for_each_entry_safe(node, q, &vha->e_dbell.head, list) {
                ql_dbg(ql_dbg_edif, vha, 0x910f,
                    "%s freeing edb_node type=%x\n",
                    __func__, node->ntype);
                qla_edb_node_free(vha, node);
        }
        spin_unlock_irqrestore(&vha->e_dbell.db_lock, flags);

        qla_edif_dbell_bsg_done(vha);
}

static struct edb_node *
qla_edb_node_alloc(scsi_qla_host_t *vha, uint32_t ntype)
{
        struct edb_node *node;

        node = kzalloc_obj(*node, GFP_ATOMIC);
        if (!node) {
                /* couldn't get space */
                ql_dbg(ql_dbg_edif, vha, 0x9100,
                    "edb node unable to be allocated\n");
                return NULL;
        }

        node->ntype = ntype;
        INIT_LIST_HEAD(&node->list);
        return node;
}

/* adds a already allocated enode to the linked list */
static bool
qla_edb_node_add(scsi_qla_host_t *vha, struct edb_node *ptr)
{
        unsigned long           flags;

        if (DBELL_INACTIVE(vha)) {
                /* doorbell list not enabled */
                ql_dbg(ql_dbg_edif, vha, 0x09102,
                    "%s doorbell not enabled\n", __func__);
                return false;
        }

        spin_lock_irqsave(&vha->e_dbell.db_lock, flags);
        list_add_tail(&ptr->list, &vha->e_dbell.head);
        spin_unlock_irqrestore(&vha->e_dbell.db_lock, flags);

        return true;
}

/* adds event to doorbell list */
void
qla_edb_eventcreate(scsi_qla_host_t *vha, uint32_t dbtype,
        uint32_t data, uint32_t data2, fc_port_t        *sfcport)
{
        struct edb_node *edbnode;
        fc_port_t *fcport = sfcport;
        port_id_t id;

        if (!vha->hw->flags.edif_enabled) {
                /* edif not enabled */
                return;
        }

        if (DBELL_INACTIVE(vha)) {
                if (fcport)
                        fcport->edif.auth_state = dbtype;
                /* doorbell list not enabled */
                ql_dbg(ql_dbg_edif, vha, 0x09102,
                    "%s doorbell not enabled (type=%d\n", __func__, dbtype);
                return;
        }

        edbnode = qla_edb_node_alloc(vha, dbtype);
        if (!edbnode) {
                ql_dbg(ql_dbg_edif, vha, 0x09102,
                    "%s unable to alloc db node\n", __func__);
                return;
        }

        if (!fcport) {
                id.b.domain = (data >> 16) & 0xff;
                id.b.area = (data >> 8) & 0xff;
                id.b.al_pa = data & 0xff;
                ql_dbg(ql_dbg_edif, vha, 0x09222,
                    "%s: Arrived s_id: %06x\n", __func__,
                    id.b24);
                fcport = qla2x00_find_fcport_by_pid(vha, &id);
                if (!fcport) {
                        ql_dbg(ql_dbg_edif, vha, 0x09102,
                            "%s can't find fcport for sid= 0x%x - ignoring\n",
                        __func__, id.b24);
                        kfree(edbnode);
                        return;
                }
        }

        /* populate the edb node */
        switch (dbtype) {
        case VND_CMD_AUTH_STATE_NEEDED:
        case VND_CMD_AUTH_STATE_SESSION_SHUTDOWN:
                edbnode->u.plogi_did.b24 = fcport->d_id.b24;
                break;
        case VND_CMD_AUTH_STATE_ELS_RCVD:
                edbnode->u.els_sid.b24 = fcport->d_id.b24;
                break;
        case VND_CMD_AUTH_STATE_SAUPDATE_COMPL:
                edbnode->u.sa_aen.port_id = fcport->d_id;
                edbnode->u.sa_aen.status =  data;
                edbnode->u.sa_aen.key_type =  data2;
                edbnode->u.sa_aen.version = EDIF_VERSION1;
                break;
        default:
                ql_dbg(ql_dbg_edif, vha, 0x09102,
                        "%s unknown type: %x\n", __func__, dbtype);
                kfree(edbnode);
                edbnode = NULL;
                break;
        }

        if (edbnode) {
                if (!qla_edb_node_add(vha, edbnode)) {
                        ql_dbg(ql_dbg_edif, vha, 0x09102,
                            "%s unable to add dbnode\n", __func__);
                        kfree(edbnode);
                        return;
                }
                ql_dbg(ql_dbg_edif, vha, 0x09102,
                    "%s Doorbell produced : type=%d %p\n", __func__, dbtype, edbnode);
                qla_edif_dbell_bsg_done(vha);
                if (fcport)
                        fcport->edif.auth_state = dbtype;
        }
}

void
qla_edif_timer(scsi_qla_host_t *vha)
{
        struct qla_hw_data *ha = vha->hw;

        if (!vha->vp_idx && N2N_TOPO(ha) && ha->flags.n2n_fw_acc_sec) {
                if (DBELL_INACTIVE(vha) &&
                    ha->edif_post_stop_cnt_down) {
                        ha->edif_post_stop_cnt_down--;

                        /*
                         * turn off auto 'Plogi Acc + secure=1' feature
                         * Set Add FW option[3]
                         * BIT_15, if.
                         */
                        if (ha->edif_post_stop_cnt_down == 0) {
                                ql_dbg(ql_dbg_async, vha, 0x911d,
                                       "%s chip reset to turn off PLOGI ACC + secure\n",
                                       __func__);
                                set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags);
                        }
                } else {
                        ha->edif_post_stop_cnt_down = 60;
                }
        }

        if (vha->e_dbell.dbell_bsg_job && time_after_eq(jiffies, vha->e_dbell.bsg_expire))
                qla_edif_dbell_bsg_done(vha);
}

static void qla_noop_sp_done(srb_t *sp, int res)
{
        sp->fcport->flags &= ~(FCF_ASYNC_SENT | FCF_ASYNC_ACTIVE);
        /* ref: INIT */
        kref_put(&sp->cmd_kref, qla2x00_sp_release);
}

/*
 * Called from work queue
 * build and send the sa_update iocb to delete an rx sa_index
 */
int
qla24xx_issue_sa_replace_iocb(scsi_qla_host_t *vha, struct qla_work_evt *e)
{
        srb_t *sp;
        fc_port_t       *fcport = NULL;
        struct srb_iocb *iocb_cmd = NULL;
        int rval = QLA_SUCCESS;
        struct  edif_sa_ctl *sa_ctl = e->u.sa_update.sa_ctl;
        uint16_t nport_handle = e->u.sa_update.nport_handle;

        ql_dbg(ql_dbg_edif, vha, 0x70e6,
            "%s: starting,  sa_ctl: %p\n", __func__, sa_ctl);

        if (!sa_ctl) {
                ql_dbg(ql_dbg_edif, vha, 0x70e6,
                    "sa_ctl allocation failed\n");
                rval = -ENOMEM;
                return rval;
        }

        fcport = sa_ctl->fcport;

        /* Alloc SRB structure */
        sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL);
        if (!sp) {
                ql_dbg(ql_dbg_edif, vha, 0x70e6,
                 "SRB allocation failed\n");
                rval = -ENOMEM;
                goto done;
        }

        fcport->flags |= FCF_ASYNC_SENT;
        iocb_cmd = &sp->u.iocb_cmd;
        iocb_cmd->u.sa_update.sa_ctl = sa_ctl;

        ql_dbg(ql_dbg_edif, vha, 0x3073,
            "Enter: SA REPL portid=%06x, sa_ctl %p, index %x, nport_handle: 0x%x\n",
            fcport->d_id.b24, sa_ctl, sa_ctl->index, nport_handle);
        /*
         * if this is a sadb cleanup delete, mark it so the isr can
         * take the correct action
         */
        if (sa_ctl->flags & EDIF_SA_CTL_FLG_CLEANUP_DEL) {
                /* mark this srb as a cleanup delete */
                sp->flags |= SRB_EDIF_CLEANUP_DELETE;
                ql_dbg(ql_dbg_edif, vha, 0x70e6,
                    "%s: sp 0x%p flagged as cleanup delete\n", __func__, sp);
        }

        sp->type = SRB_SA_REPLACE;
        sp->name = "SA_REPLACE";
        sp->fcport = fcport;
        sp->free = qla2x00_rel_sp;
        sp->done = qla_noop_sp_done;

        rval = qla2x00_start_sp(sp);

        if (rval != QLA_SUCCESS) {
                goto done_free_sp;
        }

        return rval;
done_free_sp:
        kref_put(&sp->cmd_kref, qla2x00_sp_release);
        fcport->flags &= ~FCF_ASYNC_SENT;
done:
        fcport->flags &= ~FCF_ASYNC_ACTIVE;
        return rval;
}

void qla24xx_sa_update_iocb(srb_t *sp, struct sa_update_28xx *sa_update_iocb)
{
        int     itr = 0;
        struct  scsi_qla_host           *vha = sp->vha;
        struct  qla_sa_update_frame     *sa_frame =
                &sp->u.iocb_cmd.u.sa_update.sa_frame;
        u8 flags = 0;

        switch (sa_frame->flags & (SAU_FLG_INV | SAU_FLG_TX)) {
        case 0:
                ql_dbg(ql_dbg_edif, vha, 0x911d,
                    "%s: EDIF SA UPDATE RX IOCB  vha: 0x%p  index: %d\n",
                    __func__, vha, sa_frame->fast_sa_index);
                break;
        case 1:
                ql_dbg(ql_dbg_edif, vha, 0x911d,
                    "%s: EDIF SA DELETE RX IOCB  vha: 0x%p  index: %d\n",
                    __func__, vha, sa_frame->fast_sa_index);
                flags |= SA_FLAG_INVALIDATE;
                break;
        case 2:
                ql_dbg(ql_dbg_edif, vha, 0x911d,
                    "%s: EDIF SA UPDATE TX IOCB  vha: 0x%p  index: %d\n",
                    __func__, vha, sa_frame->fast_sa_index);
                flags |= SA_FLAG_TX;
                break;
        case 3:
                ql_dbg(ql_dbg_edif, vha, 0x911d,
                    "%s: EDIF SA DELETE TX IOCB  vha: 0x%p  index: %d\n",
                    __func__, vha, sa_frame->fast_sa_index);
                flags |= SA_FLAG_TX | SA_FLAG_INVALIDATE;
                break;
        }

        sa_update_iocb->entry_type = SA_UPDATE_IOCB_TYPE;
        sa_update_iocb->entry_count = 1;
        sa_update_iocb->sys_define = 0;
        sa_update_iocb->entry_status = 0;
        sa_update_iocb->handle = sp->handle;
        sa_update_iocb->u.nport_handle = cpu_to_le16(sp->fcport->loop_id);
        sa_update_iocb->vp_index = sp->fcport->vha->vp_idx;
        sa_update_iocb->port_id[0] = sp->fcport->d_id.b.al_pa;
        sa_update_iocb->port_id[1] = sp->fcport->d_id.b.area;
        sa_update_iocb->port_id[2] = sp->fcport->d_id.b.domain;

        sa_update_iocb->flags = flags;
        sa_update_iocb->salt = cpu_to_le32(sa_frame->salt);
        sa_update_iocb->spi = cpu_to_le32(sa_frame->spi);
        sa_update_iocb->sa_index = cpu_to_le16(sa_frame->fast_sa_index);

        sa_update_iocb->sa_control |= SA_CNTL_ENC_FCSP;
        if (sp->fcport->edif.aes_gmac)
                sa_update_iocb->sa_control |= SA_CNTL_AES_GMAC;

        if (sa_frame->flags & SAU_FLG_KEY256) {
                sa_update_iocb->sa_control |= SA_CNTL_KEY256;
                for (itr = 0; itr < 32; itr++)
                        sa_update_iocb->sa_key[itr] = sa_frame->sa_key[itr];
        } else {
                sa_update_iocb->sa_control |= SA_CNTL_KEY128;
                for (itr = 0; itr < 16; itr++)
                        sa_update_iocb->sa_key[itr] = sa_frame->sa_key[itr];
        }

        ql_dbg(ql_dbg_edif, vha, 0x921d,
            "%s SAU Port ID = %02x%02x%02x, flags=%xh, index=%u, ctl=%xh, SPI 0x%x flags 0x%x hdl=%x gmac %d\n",
            __func__, sa_update_iocb->port_id[2], sa_update_iocb->port_id[1],
            sa_update_iocb->port_id[0], sa_update_iocb->flags, sa_update_iocb->sa_index,
            sa_update_iocb->sa_control, sa_update_iocb->spi, sa_frame->flags, sp->handle,
            sp->fcport->edif.aes_gmac);

        if (sa_frame->flags & SAU_FLG_TX)
                sp->fcport->edif.tx_sa_pending = 1;
        else
                sp->fcport->edif.rx_sa_pending = 1;

        sp->fcport->vha->qla_stats.control_requests++;
}

void
qla24xx_sa_replace_iocb(srb_t *sp, struct sa_update_28xx *sa_update_iocb)
{
        struct  scsi_qla_host           *vha = sp->vha;
        struct srb_iocb *srb_iocb = &sp->u.iocb_cmd;
        struct  edif_sa_ctl             *sa_ctl = srb_iocb->u.sa_update.sa_ctl;
        uint16_t nport_handle = sp->fcport->loop_id;

        sa_update_iocb->entry_type = SA_UPDATE_IOCB_TYPE;
        sa_update_iocb->entry_count = 1;
        sa_update_iocb->sys_define = 0;
        sa_update_iocb->entry_status = 0;
        sa_update_iocb->handle = sp->handle;

        sa_update_iocb->u.nport_handle = cpu_to_le16(nport_handle);

        sa_update_iocb->vp_index = sp->fcport->vha->vp_idx;
        sa_update_iocb->port_id[0] = sp->fcport->d_id.b.al_pa;
        sa_update_iocb->port_id[1] = sp->fcport->d_id.b.area;
        sa_update_iocb->port_id[2] = sp->fcport->d_id.b.domain;

        /* Invalidate the index. salt, spi, control & key are ignore */
        sa_update_iocb->flags = SA_FLAG_INVALIDATE;
        sa_update_iocb->salt = 0;
        sa_update_iocb->spi = 0;
        sa_update_iocb->sa_index = cpu_to_le16(sa_ctl->index);
        sa_update_iocb->sa_control = 0;

        ql_dbg(ql_dbg_edif, vha, 0x921d,
            "%s SAU DELETE RX Port ID = %02x:%02x:%02x, lid %d flags=%xh, index=%u, hdl=%x\n",
            __func__, sa_update_iocb->port_id[2], sa_update_iocb->port_id[1],
            sa_update_iocb->port_id[0], nport_handle, sa_update_iocb->flags,
            sa_update_iocb->sa_index, sp->handle);

        sp->fcport->vha->qla_stats.control_requests++;
}

void qla24xx_auth_els(scsi_qla_host_t *vha, void **pkt, struct rsp_que **rsp)
{
        struct purex_entry_24xx *p = *pkt;
        struct enode            *ptr;
        int             sid;
        u16 totlen;
        struct purexevent       *purex;
        struct scsi_qla_host *host = NULL;
        int rc;
        struct fc_port *fcport;
        struct qla_els_pt_arg a;
        be_id_t beid;

        memset(&a, 0, sizeof(a));

        a.els_opcode = ELS_AUTH_ELS;
        a.nport_handle = p->nport_handle;
        a.rx_xchg_address = p->rx_xchg_addr;
        a.did.b.domain = p->s_id[2];
        a.did.b.area   = p->s_id[1];
        a.did.b.al_pa  = p->s_id[0];
        a.tx_byte_count = a.tx_len = sizeof(struct fc_els_ls_rjt);
        a.tx_addr = vha->hw->elsrej.cdma;
        a.vp_idx = vha->vp_idx;
        a.control_flags = EPD_ELS_RJT;
        a.ox_id = le16_to_cpu(p->ox_id);

        sid = p->s_id[0] | (p->s_id[1] << 8) | (p->s_id[2] << 16);

        totlen = (le16_to_cpu(p->frame_size) & 0x0fff) - PURX_ELS_HEADER_SIZE;
        if (le16_to_cpu(p->status_flags) & 0x8000) {
                totlen = le16_to_cpu(p->trunc_frame_size);
                qla_els_reject_iocb(vha, (*rsp)->qpair, &a);
                __qla_consume_iocb(vha, pkt, rsp);
                return;
        }

        if (totlen > ELS_MAX_PAYLOAD) {
                ql_dbg(ql_dbg_edif, vha, 0x0910d,
                    "%s WARNING: verbose ELS frame received (totlen=%x)\n",
                    __func__, totlen);
                qla_els_reject_iocb(vha, (*rsp)->qpair, &a);
                __qla_consume_iocb(vha, pkt, rsp);
                return;
        }

        if (!vha->hw->flags.edif_enabled) {
                /* edif support not enabled */
                ql_dbg(ql_dbg_edif, vha, 0x910e, "%s edif not enabled\n",
                    __func__);
                qla_els_reject_iocb(vha, (*rsp)->qpair, &a);
                __qla_consume_iocb(vha, pkt, rsp);
                return;
        }

        ptr = qla_enode_alloc(vha, N_PUREX);
        if (!ptr) {
                ql_dbg(ql_dbg_edif, vha, 0x09109,
                    "WARNING: enode alloc failed for sid=%x\n",
                    sid);
                qla_els_reject_iocb(vha, (*rsp)->qpair, &a);
                __qla_consume_iocb(vha, pkt, rsp);
                return;
        }

        purex = &ptr->u.purexinfo;
        purex->pur_info.pur_sid = a.did;
        purex->pur_info.pur_bytes_rcvd = totlen;
        purex->pur_info.pur_rx_xchg_address = le32_to_cpu(p->rx_xchg_addr);
        purex->pur_info.pur_nphdl = le16_to_cpu(p->nport_handle);
        purex->pur_info.pur_did.b.domain =  p->d_id[2];
        purex->pur_info.pur_did.b.area =  p->d_id[1];
        purex->pur_info.pur_did.b.al_pa =  p->d_id[0];
        purex->pur_info.vp_idx = p->vp_idx;

        a.sid = purex->pur_info.pur_did;

        rc = __qla_copy_purex_to_buffer(vha, pkt, rsp, purex->msgp,
                purex->msgp_len);
        if (rc) {
                qla_els_reject_iocb(vha, (*rsp)->qpair, &a);
                qla_enode_free(vha, ptr);
                return;
        }
        beid.al_pa = purex->pur_info.pur_did.b.al_pa;
        beid.area   = purex->pur_info.pur_did.b.area;
        beid.domain = purex->pur_info.pur_did.b.domain;
        host = qla_find_host_by_d_id(vha, beid);
        if (!host) {
                ql_log(ql_log_fatal, vha, 0x508b,
                    "%s Drop ELS due to unable to find host %06x\n",
                    __func__, purex->pur_info.pur_did.b24);

                qla_els_reject_iocb(vha, (*rsp)->qpair, &a);
                qla_enode_free(vha, ptr);
                return;
        }

        fcport = qla2x00_find_fcport_by_pid(host, &purex->pur_info.pur_sid);

        if (DBELL_INACTIVE(vha)) {
                ql_dbg(ql_dbg_edif, host, 0x0910c, "%s e_dbell.db_flags =%x %06x\n",
                    __func__, host->e_dbell.db_flags,
                    fcport ? fcport->d_id.b24 : 0);

                qla_els_reject_iocb(host, (*rsp)->qpair, &a);
                qla_enode_free(host, ptr);
                return;
        }

        if (fcport && EDIF_SESSION_DOWN(fcport)) {
                ql_dbg(ql_dbg_edif, host, 0x13b6,
                    "%s terminate exchange. Send logo to 0x%x\n",
                    __func__, a.did.b24);

                a.tx_byte_count = a.tx_len = 0;
                a.tx_addr = 0;
                a.control_flags = EPD_RX_XCHG;  /* EPD_RX_XCHG = terminate cmd */
                qla_els_reject_iocb(host, (*rsp)->qpair, &a);
                qla_enode_free(host, ptr);
                /* send logo to let remote port knows to tear down session */
                fcport->send_els_logo = 1;
                qlt_schedule_sess_for_deletion(fcport);
                return;
        }

        /* add the local enode to the list */
        qla_enode_add(host, ptr);

        ql_dbg(ql_dbg_edif, host, 0x0910c,
            "%s COMPLETE purex->pur_info.pur_bytes_rcvd =%xh s:%06x -> d:%06x xchg=%xh\n",
            __func__, purex->pur_info.pur_bytes_rcvd, purex->pur_info.pur_sid.b24,
            purex->pur_info.pur_did.b24, purex->pur_info.pur_rx_xchg_address);

        qla_edb_eventcreate(host, VND_CMD_AUTH_STATE_ELS_RCVD, sid, 0, NULL);
}

static uint16_t  qla_edif_get_sa_index_from_freepool(fc_port_t *fcport, int dir)
{
        struct scsi_qla_host *vha = fcport->vha;
        struct qla_hw_data *ha = vha->hw;
        void *sa_id_map;
        unsigned long flags = 0;
        u16 sa_index;

        ql_dbg(ql_dbg_edif + ql_dbg_verbose, vha, 0x3063,
            "%s: entry\n", __func__);

        if (dir)
                sa_id_map = ha->edif_tx_sa_id_map;
        else
                sa_id_map = ha->edif_rx_sa_id_map;

        spin_lock_irqsave(&ha->sadb_fp_lock, flags);
        sa_index = find_first_zero_bit(sa_id_map, EDIF_NUM_SA_INDEX);
        if (sa_index >=  EDIF_NUM_SA_INDEX) {
                spin_unlock_irqrestore(&ha->sadb_fp_lock, flags);
                return INVALID_EDIF_SA_INDEX;
        }
        set_bit(sa_index, sa_id_map);
        spin_unlock_irqrestore(&ha->sadb_fp_lock, flags);

        if (dir)
                sa_index += EDIF_TX_SA_INDEX_BASE;

        ql_dbg(ql_dbg_edif, vha, 0x3063,
            "%s: index retrieved from free pool %d\n", __func__, sa_index);

        return sa_index;
}

/* find an sadb entry for an nport_handle */
static struct edif_sa_index_entry *
qla_edif_sadb_find_sa_index_entry(uint16_t nport_handle,
                struct list_head *sa_list)
{
        struct edif_sa_index_entry *entry;
        struct edif_sa_index_entry *tentry;
        struct list_head *indx_list = sa_list;

        list_for_each_entry_safe(entry, tentry, indx_list, next) {
                if (entry->handle == nport_handle)
                        return entry;
        }
        return NULL;
}

/* remove an sa_index from the nport_handle and return it to the free pool */
static int qla_edif_sadb_delete_sa_index(fc_port_t *fcport, uint16_t nport_handle,
                uint16_t sa_index)
{
        struct edif_sa_index_entry *entry;
        struct list_head *sa_list;
        int dir = (sa_index < EDIF_TX_SA_INDEX_BASE) ? 0 : 1;
        int slot = 0;
        int free_slot_count = 0;
        scsi_qla_host_t *vha = fcport->vha;
        struct qla_hw_data *ha = vha->hw;
        unsigned long flags = 0;

        ql_dbg(ql_dbg_edif, vha, 0x3063,
            "%s: entry\n", __func__);

        if (dir)
                sa_list = &ha->sadb_tx_index_list;
        else
                sa_list = &ha->sadb_rx_index_list;

        entry = qla_edif_sadb_find_sa_index_entry(nport_handle, sa_list);
        if (!entry) {
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: no entry found for nport_handle 0x%x\n",
                    __func__, nport_handle);
                return -1;
        }

        spin_lock_irqsave(&ha->sadb_lock, flags);
        /*
         * each tx/rx direction has up to 2 sa indexes/slots. 1 slot for in flight traffic
         * the other is use at re-key time.
         */
        for (slot = 0; slot < 2; slot++) {
                if (entry->sa_pair[slot].sa_index == sa_index) {
                        entry->sa_pair[slot].sa_index = INVALID_EDIF_SA_INDEX;
                        entry->sa_pair[slot].spi = 0;
                        free_slot_count++;
                        qla_edif_add_sa_index_to_freepool(fcport, dir, sa_index);
                } else if (entry->sa_pair[slot].sa_index == INVALID_EDIF_SA_INDEX) {
                        free_slot_count++;
                }
        }

        if (free_slot_count == 2) {
                list_del(&entry->next);
                kfree(entry);
        }
        spin_unlock_irqrestore(&ha->sadb_lock, flags);

        ql_dbg(ql_dbg_edif, vha, 0x3063,
            "%s: sa_index %d removed, free_slot_count: %d\n",
            __func__, sa_index, free_slot_count);

        return 0;
}

void
qla28xx_sa_update_iocb_entry(scsi_qla_host_t *v, struct req_que *req,
        struct sa_update_28xx *pkt)
{
        const char *func = "SA_UPDATE_RESPONSE_IOCB";
        srb_t *sp;
        struct edif_sa_ctl *sa_ctl;
        int old_sa_deleted = 1;
        uint16_t nport_handle;
        struct scsi_qla_host *vha;

        sp = qla2x00_get_sp_from_handle(v, func, req, pkt);

        if (!sp) {
                ql_dbg(ql_dbg_edif, v, 0x3063,
                        "%s: no sp found for pkt\n", __func__);
                return;
        }
        /* use sp->vha due to npiv */
        vha = sp->vha;

        switch (pkt->flags & (SA_FLAG_INVALIDATE | SA_FLAG_TX)) {
        case 0:
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: EDIF SA UPDATE RX IOCB  vha: 0x%p  index: %d\n",
                    __func__, vha, pkt->sa_index);
                break;
        case 1:
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: EDIF SA DELETE RX IOCB  vha: 0x%p  index: %d\n",
                    __func__, vha, pkt->sa_index);
                break;
        case 2:
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: EDIF SA UPDATE TX IOCB  vha: 0x%p  index: %d\n",
                    __func__, vha, pkt->sa_index);
                break;
        case 3:
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: EDIF SA DELETE TX IOCB  vha: 0x%p  index: %d\n",
                    __func__, vha, pkt->sa_index);
                break;
        }

        /*
         * dig the nport handle out of the iocb, fcport->loop_id can not be trusted
         * to be correct during cleanup sa_update iocbs.
         */
        nport_handle = sp->fcport->loop_id;

        ql_dbg(ql_dbg_edif, vha, 0x3063,
            "%s: %8phN comp status=%x old_sa_info=%x new_sa_info=%x lid %d, index=0x%x pkt_flags %xh hdl=%x\n",
            __func__, sp->fcport->port_name, pkt->u.comp_sts, pkt->old_sa_info, pkt->new_sa_info,
            nport_handle, pkt->sa_index, pkt->flags, sp->handle);

        /* if rx delete, remove the timer */
        if ((pkt->flags & (SA_FLAG_INVALIDATE | SA_FLAG_TX)) ==  SA_FLAG_INVALIDATE) {
                struct edif_list_entry *edif_entry;

                sp->fcport->flags &= ~(FCF_ASYNC_SENT | FCF_ASYNC_ACTIVE);

                edif_entry = qla_edif_list_find_sa_index(sp->fcport, nport_handle);
                if (edif_entry) {
                        ql_dbg(ql_dbg_edif, vha, 0x5033,
                            "%s: removing edif_entry %p, new sa_index: 0x%x\n",
                            __func__, edif_entry, pkt->sa_index);
                        qla_edif_list_delete_sa_index(sp->fcport, edif_entry);
                        timer_shutdown(&edif_entry->timer);

                        ql_dbg(ql_dbg_edif, vha, 0x5033,
                            "%s: releasing edif_entry %p, new sa_index: 0x%x\n",
                            __func__, edif_entry, pkt->sa_index);

                        kfree(edif_entry);
                }
        }

        /*
         * if this is a delete for either tx or rx, make sure it succeeded.
         * The new_sa_info field should be 0xffff on success
         */
        if (pkt->flags & SA_FLAG_INVALIDATE)
                old_sa_deleted = (le16_to_cpu(pkt->new_sa_info) == 0xffff) ? 1 : 0;

        /* Process update and delete the same way */

        /* If this is an sadb cleanup delete, bypass sending events to IPSEC */
        if (sp->flags & SRB_EDIF_CLEANUP_DELETE) {
                sp->fcport->flags &= ~(FCF_ASYNC_SENT | FCF_ASYNC_ACTIVE);
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: nph 0x%x, sa_index %d removed from fw\n",
                    __func__, sp->fcport->loop_id, pkt->sa_index);

        } else if ((pkt->entry_status == 0) && (pkt->u.comp_sts == 0) &&
            old_sa_deleted) {
                /*
                 * Note: Wa are only keeping track of latest SA,
                 * so we know when we can start enableing encryption per I/O.
                 * If all SA's get deleted, let FW reject the IOCB.

                 * TODO: edif: don't set enabled here I think
                 * TODO: edif: prli complete is where it should be set
                 */
                ql_dbg(ql_dbg_edif + ql_dbg_verbose, vha, 0x3063,
                        "SA(%x)updated for s_id %02x%02x%02x\n",
                        pkt->new_sa_info,
                        pkt->port_id[2], pkt->port_id[1], pkt->port_id[0]);
                sp->fcport->edif.enable = 1;
                if (pkt->flags & SA_FLAG_TX) {
                        sp->fcport->edif.tx_sa_set = 1;
                        sp->fcport->edif.tx_sa_pending = 0;
                        qla_edb_eventcreate(vha, VND_CMD_AUTH_STATE_SAUPDATE_COMPL,
                                QL_VND_SA_STAT_SUCCESS,
                                QL_VND_TX_SA_KEY, sp->fcport);
                } else {
                        sp->fcport->edif.rx_sa_set = 1;
                        sp->fcport->edif.rx_sa_pending = 0;
                        qla_edb_eventcreate(vha, VND_CMD_AUTH_STATE_SAUPDATE_COMPL,
                                QL_VND_SA_STAT_SUCCESS,
                                QL_VND_RX_SA_KEY, sp->fcport);
                }
        } else {
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: %8phN SA update FAILED: sa_index: %d, new_sa_info %d, %02x%02x%02x\n",
                    __func__, sp->fcport->port_name, pkt->sa_index, pkt->new_sa_info,
                    pkt->port_id[2], pkt->port_id[1], pkt->port_id[0]);

                if (pkt->flags & SA_FLAG_TX)
                        qla_edb_eventcreate(vha, VND_CMD_AUTH_STATE_SAUPDATE_COMPL,
                                (le16_to_cpu(pkt->u.comp_sts) << 16) | QL_VND_SA_STAT_FAILED,
                                QL_VND_TX_SA_KEY, sp->fcport);
                else
                        qla_edb_eventcreate(vha, VND_CMD_AUTH_STATE_SAUPDATE_COMPL,
                                (le16_to_cpu(pkt->u.comp_sts) << 16) | QL_VND_SA_STAT_FAILED,
                                QL_VND_RX_SA_KEY, sp->fcport);
        }

        /* for delete, release sa_ctl, sa_index */
        if (pkt->flags & SA_FLAG_INVALIDATE) {
                /* release the sa_ctl */
                sa_ctl = qla_edif_find_sa_ctl_by_index(sp->fcport,
                    le16_to_cpu(pkt->sa_index), (pkt->flags & SA_FLAG_TX));
                if (sa_ctl &&
                    qla_edif_find_sa_ctl_by_index(sp->fcport, sa_ctl->index,
                        (pkt->flags & SA_FLAG_TX)) != NULL) {
                        ql_dbg(ql_dbg_edif + ql_dbg_verbose, vha, 0x3063,
                            "%s: freeing sa_ctl for index %d\n",
                            __func__, sa_ctl->index);
                        qla_edif_free_sa_ctl(sp->fcport, sa_ctl, sa_ctl->index);
                } else {
                        ql_dbg(ql_dbg_edif, vha, 0x3063,
                            "%s: sa_ctl NOT freed, sa_ctl: %p\n",
                            __func__, sa_ctl);
                }
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: freeing sa_index %d, nph: 0x%x\n",
                    __func__, le16_to_cpu(pkt->sa_index), nport_handle);
                qla_edif_sadb_delete_sa_index(sp->fcport, nport_handle,
                    le16_to_cpu(pkt->sa_index));
        /*
         * check for a failed sa_update and remove
         * the sadb entry.
         */
        } else if (pkt->u.comp_sts) {
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: freeing sa_index %d, nph: 0x%x\n",
                    __func__, pkt->sa_index, nport_handle);
                qla_edif_sadb_delete_sa_index(sp->fcport, nport_handle,
                    le16_to_cpu(pkt->sa_index));
                switch (le16_to_cpu(pkt->u.comp_sts)) {
                case CS_PORT_EDIF_UNAVAIL:
                case CS_PORT_EDIF_LOGOUT:
                        qlt_schedule_sess_for_deletion(sp->fcport);
                        break;
                default:
                        break;
                }
        }

        sp->done(sp, 0);
}

/**
 * qla28xx_start_scsi_edif() - Send a SCSI type 6 command to the ISP
 * @sp: command to send to the ISP
 *
 * Return: non-zero if a failure occurred, else zero.
 */
int
qla28xx_start_scsi_edif(srb_t *sp)
{
        int             nseg;
        unsigned long   flags;
        struct scsi_cmnd *cmd;
        uint32_t        *clr_ptr;
        uint32_t        index, i;
        uint32_t        handle;
        uint16_t        cnt;
        int16_t        req_cnt;
        uint16_t        tot_dsds;
        __be32 *fcp_dl;
        uint8_t additional_cdb_len;
        struct ct6_dsd *ctx;
        struct scsi_qla_host *vha = sp->vha;
        struct qla_hw_data *ha = vha->hw;
        struct cmd_type_6 *cmd_pkt;
        struct dsd64    *cur_dsd;
        uint8_t         avail_dsds = 0;
        struct scatterlist *sg;
        struct req_que *req = sp->qpair->req;
        spinlock_t *lock = sp->qpair->qp_lock_ptr;

        /* Setup device pointers. */
        cmd = GET_CMD_SP(sp);

        /* So we know we haven't pci_map'ed anything yet */
        tot_dsds = 0;

        /* Send marker if required */
        if (vha->marker_needed != 0) {
                if (qla2x00_marker(vha, sp->qpair, 0, 0, MK_SYNC_ALL) !=
                        QLA_SUCCESS) {
                        ql_log(ql_log_warn, vha, 0x300c,
                            "qla2x00_marker failed for cmd=%p.\n", cmd);
                        return QLA_FUNCTION_FAILED;
                }
                vha->marker_needed = 0;
        }

        /* Acquire ring specific lock */
        spin_lock_irqsave(lock, flags);

        /* Check for room in outstanding command list. */
        handle = req->current_outstanding_cmd;
        for (index = 1; index < req->num_outstanding_cmds; index++) {
                handle++;
                if (handle == req->num_outstanding_cmds)
                        handle = 1;
                if (!req->outstanding_cmds[handle])
                        break;
        }
        if (index == req->num_outstanding_cmds)
                goto queuing_error;

        /* Map the sg table so we have an accurate count of sg entries needed */
        if (scsi_sg_count(cmd)) {
                nseg = dma_map_sg(&ha->pdev->dev, scsi_sglist(cmd),
                    scsi_sg_count(cmd), cmd->sc_data_direction);
                if (unlikely(!nseg))
                        goto queuing_error;
        } else {
                nseg = 0;
        }

        tot_dsds = nseg;
        req_cnt = qla24xx_calc_iocbs(vha, tot_dsds);

        sp->iores.res_type = RESOURCE_IOCB | RESOURCE_EXCH;
        sp->iores.exch_cnt = 1;
        sp->iores.iocb_cnt = req_cnt;
        if (qla_get_fw_resources(sp->qpair, &sp->iores))
                goto queuing_error;

        if (req->cnt < (req_cnt + 2)) {
                cnt = IS_SHADOW_REG_CAPABLE(ha) ? *req->out_ptr :
                    rd_reg_dword(req->req_q_out);
                if (req->ring_index < cnt)
                        req->cnt = cnt - req->ring_index;
                else
                        req->cnt = req->length -
                            (req->ring_index - cnt);
                if (req->cnt < (req_cnt + 2))
                        goto queuing_error;
        }

        if (qla_get_buf(vha, sp->qpair, &sp->u.scmd.buf_dsc)) {
                ql_log(ql_log_fatal, vha, 0x3011,
                    "Failed to allocate buf for fcp_cmnd for cmd=%p.\n", cmd);
                goto queuing_error;
        }

        sp->flags |= SRB_GOT_BUF;
        ctx = &sp->u.scmd.ct6_ctx;
        ctx->fcp_cmnd = sp->u.scmd.buf_dsc.buf;
        ctx->fcp_cmnd_dma = sp->u.scmd.buf_dsc.buf_dma;

        if (cmd->cmd_len > 16) {
                additional_cdb_len = cmd->cmd_len - 16;
                if ((cmd->cmd_len % 4) != 0) {
                        /*
                         * SCSI command bigger than 16 bytes must be
                         * multiple of 4
                         */
                        ql_log(ql_log_warn, vha, 0x3012,
                            "scsi cmd len %d not multiple of 4 for cmd=%p.\n",
                            cmd->cmd_len, cmd);
                        goto queuing_error_fcp_cmnd;
                }
                ctx->fcp_cmnd_len = 12 + cmd->cmd_len + 4;
        } else {
                additional_cdb_len = 0;
                ctx->fcp_cmnd_len = 12 + 16 + 4;
        }

        cmd_pkt = (struct cmd_type_6 *)req->ring_ptr;
        cmd_pkt->handle = make_handle(req->id, handle);

        /*
         * Zero out remaining portion of packet.
         * tagged queuing modifier -- default is TSK_SIMPLE (0).
         */
        clr_ptr = (uint32_t *)cmd_pkt + 2;
        memset(clr_ptr, 0, REQUEST_ENTRY_SIZE - 8);
        cmd_pkt->dseg_count = cpu_to_le16(tot_dsds);

        /* No data transfer */
        if (!scsi_bufflen(cmd) || cmd->sc_data_direction == DMA_NONE) {
                cmd_pkt->byte_count = cpu_to_le32(0);
                goto no_dsds;
        }

        /* Set transfer direction */
        if (cmd->sc_data_direction == DMA_TO_DEVICE) {
                cmd_pkt->control_flags = cpu_to_le16(CF_WRITE_DATA);
                vha->qla_stats.output_bytes += scsi_bufflen(cmd);
                vha->qla_stats.output_requests++;
                sp->fcport->edif.tx_bytes += scsi_bufflen(cmd);
        } else if (cmd->sc_data_direction == DMA_FROM_DEVICE) {
                cmd_pkt->control_flags = cpu_to_le16(CF_READ_DATA);
                vha->qla_stats.input_bytes += scsi_bufflen(cmd);
                vha->qla_stats.input_requests++;
                sp->fcport->edif.rx_bytes += scsi_bufflen(cmd);
        }

        cmd_pkt->control_flags |= cpu_to_le16(CF_EN_EDIF);
        cmd_pkt->control_flags &= ~(cpu_to_le16(CF_NEW_SA));

        /* One DSD is available in the Command Type 6 IOCB */
        avail_dsds = 1;
        cur_dsd = &cmd_pkt->fcp_dsd;

        /* Load data segments */
        scsi_for_each_sg(cmd, sg, tot_dsds, i) {
                dma_addr_t      sle_dma;
                cont_a64_entry_t *cont_pkt;

                /* Allocate additional continuation packets? */
                if (avail_dsds == 0) {
                        /*
                         * Five DSDs are available in the Continuation
                         * Type 1 IOCB.
                         */
                        cont_pkt = qla2x00_prep_cont_type1_iocb(vha, req);
                        cur_dsd = cont_pkt->dsd;
                        avail_dsds = 5;
                }

                sle_dma = sg_dma_address(sg);
                put_unaligned_le64(sle_dma, &cur_dsd->address);
                cur_dsd->length = cpu_to_le32(sg_dma_len(sg));
                cur_dsd++;
                avail_dsds--;
        }

no_dsds:
        /* Set NPORT-ID and LUN number*/
        cmd_pkt->nport_handle = cpu_to_le16(sp->fcport->loop_id);
        cmd_pkt->port_id[0] = sp->fcport->d_id.b.al_pa;
        cmd_pkt->port_id[1] = sp->fcport->d_id.b.area;
        cmd_pkt->port_id[2] = sp->fcport->d_id.b.domain;
        cmd_pkt->vp_index = sp->vha->vp_idx;

        cmd_pkt->entry_type = COMMAND_TYPE_6;

        /* Set total data segment count. */
        cmd_pkt->entry_count = (uint8_t)req_cnt;

        int_to_scsilun(cmd->device->lun, &cmd_pkt->lun);
        host_to_fcp_swap((uint8_t *)&cmd_pkt->lun, sizeof(cmd_pkt->lun));

        /* build FCP_CMND IU */
        int_to_scsilun(cmd->device->lun, &ctx->fcp_cmnd->lun);
        ctx->fcp_cmnd->additional_cdb_len = additional_cdb_len;

        if (cmd->sc_data_direction == DMA_TO_DEVICE)
                ctx->fcp_cmnd->additional_cdb_len |= 1;
        else if (cmd->sc_data_direction == DMA_FROM_DEVICE)
                ctx->fcp_cmnd->additional_cdb_len |= 2;

        /* Populate the FCP_PRIO. */
        if (ha->flags.fcp_prio_enabled)
                ctx->fcp_cmnd->task_attribute |=
                    sp->fcport->fcp_prio << 3;

        memcpy(ctx->fcp_cmnd->cdb, cmd->cmnd, cmd->cmd_len);

        fcp_dl = (__be32 *)(ctx->fcp_cmnd->cdb + 16 +
            additional_cdb_len);
        *fcp_dl = htonl((uint32_t)scsi_bufflen(cmd));

        cmd_pkt->fcp_cmnd_dseg_len = cpu_to_le16(ctx->fcp_cmnd_len);
        put_unaligned_le64(ctx->fcp_cmnd_dma, &cmd_pkt->fcp_cmnd_dseg_address);

        cmd_pkt->byte_count = cpu_to_le32((uint32_t)scsi_bufflen(cmd));
        /* Set total data segment count. */
        cmd_pkt->entry_count = (uint8_t)req_cnt;
        cmd_pkt->entry_status = 0;

        /* Build command packet. */
        req->current_outstanding_cmd = handle;
        req->outstanding_cmds[handle] = sp;
        sp->handle = handle;
        cmd->host_scribble = (unsigned char *)(unsigned long)handle;
        req->cnt -= req_cnt;

        /* Adjust ring index. */
        wmb();
        req->ring_index++;
        if (req->ring_index == req->length) {
                req->ring_index = 0;
                req->ring_ptr = req->ring;
        } else {
                req->ring_ptr++;
        }

        sp->qpair->cmd_cnt++;
        /* Set chip new ring index. */
        wrt_reg_dword(req->req_q_in, req->ring_index);

        spin_unlock_irqrestore(lock, flags);

        return QLA_SUCCESS;

queuing_error_fcp_cmnd:
queuing_error:
        if (tot_dsds)
                scsi_dma_unmap(cmd);

        qla_put_buf(sp->qpair, &sp->u.scmd.buf_dsc);
        qla_put_fw_resources(sp->qpair, &sp->iores);
        spin_unlock_irqrestore(lock, flags);

        return QLA_FUNCTION_FAILED;
}

/**********************************************
 * edif update/delete sa_index list functions *
 **********************************************/

/* clear the edif_indx_list for this port */
void qla_edif_list_del(fc_port_t *fcport)
{
        struct edif_list_entry *indx_lst;
        struct edif_list_entry *tindx_lst;
        struct list_head *indx_list = &fcport->edif.edif_indx_list;
        unsigned long flags = 0;

        spin_lock_irqsave(&fcport->edif.indx_list_lock, flags);
        list_for_each_entry_safe(indx_lst, tindx_lst, indx_list, next) {
                list_del(&indx_lst->next);
                kfree(indx_lst);
        }
        spin_unlock_irqrestore(&fcport->edif.indx_list_lock, flags);
}

/******************
 * SADB functions *
 ******************/

/* allocate/retrieve an sa_index for a given spi */
static uint16_t qla_edif_sadb_get_sa_index(fc_port_t *fcport,
                struct qla_sa_update_frame *sa_frame)
{
        struct edif_sa_index_entry *entry;
        struct list_head *sa_list;
        uint16_t sa_index;
        int dir = sa_frame->flags & SAU_FLG_TX;
        int slot = 0;
        int free_slot = -1;
        scsi_qla_host_t *vha = fcport->vha;
        struct qla_hw_data *ha = vha->hw;
        unsigned long flags = 0;
        uint16_t nport_handle = fcport->loop_id;

        ql_dbg(ql_dbg_edif, vha, 0x3063,
            "%s: entry  fc_port: %p, nport_handle: 0x%x\n",
            __func__, fcport, nport_handle);

        if (dir)
                sa_list = &ha->sadb_tx_index_list;
        else
                sa_list = &ha->sadb_rx_index_list;

        entry = qla_edif_sadb_find_sa_index_entry(nport_handle, sa_list);
        if (!entry) {
                if ((sa_frame->flags & (SAU_FLG_TX | SAU_FLG_INV)) == SAU_FLG_INV) {
                        ql_dbg(ql_dbg_edif, vha, 0x3063,
                            "%s: rx delete request with no entry\n", __func__);
                        return RX_DELETE_NO_EDIF_SA_INDEX;
                }

                /* if there is no entry for this nport, add one */
                entry = kzalloc_obj(struct edif_sa_index_entry, GFP_ATOMIC);
                if (!entry)
                        return INVALID_EDIF_SA_INDEX;

                sa_index = qla_edif_get_sa_index_from_freepool(fcport, dir);
                if (sa_index == INVALID_EDIF_SA_INDEX) {
                        kfree(entry);
                        return INVALID_EDIF_SA_INDEX;
                }

                INIT_LIST_HEAD(&entry->next);
                entry->handle = nport_handle;
                entry->fcport = fcport;
                entry->sa_pair[0].spi = sa_frame->spi;
                entry->sa_pair[0].sa_index = sa_index;
                entry->sa_pair[1].spi = 0;
                entry->sa_pair[1].sa_index = INVALID_EDIF_SA_INDEX;
                spin_lock_irqsave(&ha->sadb_lock, flags);
                list_add_tail(&entry->next, sa_list);
                spin_unlock_irqrestore(&ha->sadb_lock, flags);
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: Created new sadb entry for nport_handle 0x%x, spi 0x%x, returning sa_index %d\n",
                    __func__, nport_handle, sa_frame->spi, sa_index);

                return sa_index;
        }

        spin_lock_irqsave(&ha->sadb_lock, flags);

        /* see if we already have an entry for this spi */
        for (slot = 0; slot < 2; slot++) {
                if (entry->sa_pair[slot].sa_index == INVALID_EDIF_SA_INDEX) {
                        free_slot = slot;
                } else {
                        if (entry->sa_pair[slot].spi == sa_frame->spi) {
                                spin_unlock_irqrestore(&ha->sadb_lock, flags);
                                ql_dbg(ql_dbg_edif, vha, 0x3063,
                                    "%s: sadb slot %d entry for lid 0x%x, spi 0x%x found, sa_index %d\n",
                                    __func__, slot, entry->handle, sa_frame->spi,
                                    entry->sa_pair[slot].sa_index);
                                return entry->sa_pair[slot].sa_index;
                        }
                }
        }
        spin_unlock_irqrestore(&ha->sadb_lock, flags);

        /* both slots are used */
        if (free_slot == -1) {
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: WARNING: No free slots in sadb for nport_handle 0x%x, spi: 0x%x\n",
                    __func__, entry->handle, sa_frame->spi);
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: Slot 0  spi: 0x%x  sa_index: %d,  Slot 1  spi: 0x%x  sa_index: %d\n",
                    __func__, entry->sa_pair[0].spi, entry->sa_pair[0].sa_index,
                    entry->sa_pair[1].spi, entry->sa_pair[1].sa_index);

                return INVALID_EDIF_SA_INDEX;
        }

        /* there is at least one free slot, use it */
        sa_index = qla_edif_get_sa_index_from_freepool(fcport, dir);
        if (sa_index == INVALID_EDIF_SA_INDEX) {
                ql_dbg(ql_dbg_edif, fcport->vha, 0x3063,
                    "%s: empty freepool!!\n", __func__);
                return INVALID_EDIF_SA_INDEX;
        }

        spin_lock_irqsave(&ha->sadb_lock, flags);
        entry->sa_pair[free_slot].spi = sa_frame->spi;
        entry->sa_pair[free_slot].sa_index = sa_index;
        spin_unlock_irqrestore(&ha->sadb_lock, flags);
        ql_dbg(ql_dbg_edif, fcport->vha, 0x3063,
            "%s: sadb slot %d entry for nport_handle 0x%x, spi 0x%x added, returning sa_index %d\n",
            __func__, free_slot, entry->handle, sa_frame->spi, sa_index);

        return sa_index;
}

/* release any sadb entries -- only done at teardown */
void qla_edif_sadb_release(struct qla_hw_data *ha)
{
        struct edif_sa_index_entry *entry, *tmp;

        list_for_each_entry_safe(entry, tmp, &ha->sadb_rx_index_list, next) {
                list_del(&entry->next);
                kfree(entry);
        }

        list_for_each_entry_safe(entry, tmp, &ha->sadb_tx_index_list, next) {
                list_del(&entry->next);
                kfree(entry);
        }
}

/**************************
 * sadb freepool functions
 **************************/

/* build the rx and tx sa_index free pools -- only done at fcport init */
int qla_edif_sadb_build_free_pool(struct qla_hw_data *ha)
{
        ha->edif_tx_sa_id_map =
            kzalloc_objs(long, BITS_TO_LONGS(EDIF_NUM_SA_INDEX));

        if (!ha->edif_tx_sa_id_map) {
                ql_log_pci(ql_log_fatal, ha->pdev, 0x0009,
                    "Unable to allocate memory for sadb tx.\n");
                return -ENOMEM;
        }

        ha->edif_rx_sa_id_map =
            kzalloc_objs(long, BITS_TO_LONGS(EDIF_NUM_SA_INDEX));
        if (!ha->edif_rx_sa_id_map) {
                kfree(ha->edif_tx_sa_id_map);
                ha->edif_tx_sa_id_map = NULL;
                ql_log_pci(ql_log_fatal, ha->pdev, 0x0009,
                    "Unable to allocate memory for sadb rx.\n");
                return -ENOMEM;
        }
        return 0;
}

/* release the free pool - only done during fcport teardown */
void qla_edif_sadb_release_free_pool(struct qla_hw_data *ha)
{
        kfree(ha->edif_tx_sa_id_map);
        ha->edif_tx_sa_id_map = NULL;
        kfree(ha->edif_rx_sa_id_map);
        ha->edif_rx_sa_id_map = NULL;
}

static void __chk_edif_rx_sa_delete_pending(scsi_qla_host_t *vha,
                fc_port_t *fcport, uint32_t handle, uint16_t sa_index)
{
        struct edif_list_entry *edif_entry;
        struct edif_sa_ctl *sa_ctl;
        uint16_t delete_sa_index = INVALID_EDIF_SA_INDEX;
        unsigned long flags = 0;
        uint16_t nport_handle = fcport->loop_id;
        uint16_t cached_nport_handle;

        spin_lock_irqsave(&fcport->edif.indx_list_lock, flags);
        edif_entry = qla_edif_list_find_sa_index(fcport, nport_handle);
        if (!edif_entry) {
                spin_unlock_irqrestore(&fcport->edif.indx_list_lock, flags);
                return;         /* no pending delete for this handle */
        }

        /*
         * check for no pending delete for this index or iocb does not
         * match rx sa_index
         */
        if (edif_entry->delete_sa_index == INVALID_EDIF_SA_INDEX ||
            edif_entry->update_sa_index != sa_index) {
                spin_unlock_irqrestore(&fcport->edif.indx_list_lock, flags);
                return;
        }

        /*
         * wait until we have seen at least EDIF_DELAY_COUNT transfers before
         * queueing RX delete
         */
        if (edif_entry->count++ < EDIF_RX_DELETE_FILTER_COUNT) {
                spin_unlock_irqrestore(&fcport->edif.indx_list_lock, flags);
                return;
        }

        ql_dbg(ql_dbg_edif, vha, 0x5033,
            "%s: invalidating delete_sa_index,  update_sa_index: 0x%x sa_index: 0x%x, delete_sa_index: 0x%x\n",
            __func__, edif_entry->update_sa_index, sa_index, edif_entry->delete_sa_index);

        delete_sa_index = edif_entry->delete_sa_index;
        edif_entry->delete_sa_index = INVALID_EDIF_SA_INDEX;
        cached_nport_handle = edif_entry->handle;
        spin_unlock_irqrestore(&fcport->edif.indx_list_lock, flags);

        /* sanity check on the nport handle */
        if (nport_handle != cached_nport_handle) {
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: POST SA DELETE nport_handle mismatch: lid: 0x%x, edif_entry nph: 0x%x\n",
                    __func__, nport_handle, cached_nport_handle);
        }

        /* find the sa_ctl for the delete and schedule the delete */
        sa_ctl = qla_edif_find_sa_ctl_by_index(fcport, delete_sa_index, 0);
        if (sa_ctl) {
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: POST SA DELETE sa_ctl: %p, index recvd %d\n",
                    __func__, sa_ctl, sa_index);
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "delete index %d, update index: %d, nport handle: 0x%x, handle: 0x%x\n",
                    delete_sa_index,
                    edif_entry->update_sa_index, nport_handle, handle);

                sa_ctl->flags = EDIF_SA_CTL_FLG_DEL;
                set_bit(EDIF_SA_CTL_REPL, &sa_ctl->state);
                qla_post_sa_replace_work(fcport->vha, fcport,
                    nport_handle, sa_ctl);
        } else {
                ql_dbg(ql_dbg_edif, vha, 0x3063,
                    "%s: POST SA DELETE sa_ctl not found for delete_sa_index: %d\n",
                    __func__, delete_sa_index);
        }
}

void qla_chk_edif_rx_sa_delete_pending(scsi_qla_host_t *vha,
                srb_t *sp, struct sts_entry_24xx *sts24)
{
        fc_port_t *fcport = sp->fcport;
        /* sa_index used by this iocb */
        struct scsi_cmnd *cmd = GET_CMD_SP(sp);
        uint32_t handle;

        handle = (uint32_t)LSW(sts24->handle);

        /* find out if this status iosb is for a scsi read */
        if (cmd->sc_data_direction != DMA_FROM_DEVICE)
                return;

        return __chk_edif_rx_sa_delete_pending(vha, fcport, handle,
           le16_to_cpu(sts24->edif_sa_index));
}

void qlt_chk_edif_rx_sa_delete_pending(scsi_qla_host_t *vha, fc_port_t *fcport,
                struct ctio7_from_24xx *pkt)
{
        __chk_edif_rx_sa_delete_pending(vha, fcport,
            pkt->handle, le16_to_cpu(pkt->edif_sa_index));
}

static void qla_parse_auth_els_ctl(struct srb *sp)
{
        struct qla_els_pt_arg *a = &sp->u.bsg_cmd.u.els_arg;
        struct bsg_job *bsg_job = sp->u.bsg_cmd.bsg_job;
        struct fc_bsg_request *request = bsg_job->request;
        struct qla_bsg_auth_els_request *p =
            (struct qla_bsg_auth_els_request *)bsg_job->request;

        a->tx_len = a->tx_byte_count = sp->remap.req.len;
        a->tx_addr = sp->remap.req.dma;
        a->rx_len = a->rx_byte_count = sp->remap.rsp.len;
        a->rx_addr = sp->remap.rsp.dma;

        if (p->e.sub_cmd == SEND_ELS_REPLY) {
                a->control_flags = p->e.extra_control_flags << 13;
                a->rx_xchg_address = cpu_to_le32(p->e.extra_rx_xchg_address);
                if (p->e.extra_control_flags == BSG_CTL_FLAG_LS_ACC)
                        a->els_opcode = ELS_LS_ACC;
                else if (p->e.extra_control_flags == BSG_CTL_FLAG_LS_RJT)
                        a->els_opcode = ELS_LS_RJT;
        }
        a->did = sp->fcport->d_id;
        a->els_opcode =  request->rqst_data.h_els.command_code;
        a->nport_handle = cpu_to_le16(sp->fcport->loop_id);
        a->vp_idx = sp->vha->vp_idx;
}

int qla_edif_process_els(scsi_qla_host_t *vha, struct bsg_job *bsg_job)
{
        struct fc_bsg_request *bsg_request = bsg_job->request;
        struct fc_bsg_reply *bsg_reply = bsg_job->reply;
        fc_port_t *fcport = NULL;
        struct qla_hw_data *ha = vha->hw;
        srb_t *sp;
        int rval =  (DID_ERROR << 16), cnt;
        port_id_t d_id;
        struct qla_bsg_auth_els_request *p =
            (struct qla_bsg_auth_els_request *)bsg_job->request;
        struct qla_bsg_auth_els_reply *rpl =
            (struct qla_bsg_auth_els_reply *)bsg_job->reply;

        rpl->version = EDIF_VERSION1;

        d_id.b.al_pa = bsg_request->rqst_data.h_els.port_id[2];
        d_id.b.area = bsg_request->rqst_data.h_els.port_id[1];
        d_id.b.domain = bsg_request->rqst_data.h_els.port_id[0];

        /* find matching d_id in fcport list */
        fcport = qla2x00_find_fcport_by_pid(vha, &d_id);
        if (!fcport) {
                ql_dbg(ql_dbg_edif, vha, 0x911a,
                    "%s fcport not find online portid=%06x.\n",
                    __func__, d_id.b24);
                SET_DID_STATUS(bsg_reply->result, DID_ERROR);
                return -EIO;
        }

        if (qla_bsg_check(vha, bsg_job, fcport))
                return 0;

        if (EDIF_SESS_DELETE(fcport)) {
                ql_dbg(ql_dbg_edif, vha, 0x910d,
                    "%s ELS code %x, no loop id.\n", __func__,
                    bsg_request->rqst_data.r_els.els_code);
                SET_DID_STATUS(bsg_reply->result, DID_BAD_TARGET);
                return -ENXIO;
        }

        if (!vha->flags.online) {
                ql_log(ql_log_warn, vha, 0x7005, "Host not online.\n");
                SET_DID_STATUS(bsg_reply->result, DID_BAD_TARGET);
                rval = -EIO;
                goto done;
        }

        /* pass through is supported only for ISP 4Gb or higher */
        if (!IS_FWI2_CAPABLE(ha)) {
                ql_dbg(ql_dbg_user, vha, 0x7001,
                    "ELS passthru not supported for ISP23xx based adapters.\n");
                SET_DID_STATUS(bsg_reply->result, DID_BAD_TARGET);
                rval = -EPERM;
                goto done;
        }

        sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL);
        if (!sp) {
                ql_dbg(ql_dbg_user, vha, 0x7004,
                    "Failed get sp pid=%06x\n", fcport->d_id.b24);
                rval = -ENOMEM;
                SET_DID_STATUS(bsg_reply->result, DID_IMM_RETRY);
                goto done;
        }

        sp->remap.req.len = bsg_job->request_payload.payload_len;
        sp->remap.req.buf = dma_pool_alloc(ha->purex_dma_pool,
            GFP_KERNEL, &sp->remap.req.dma);
        if (!sp->remap.req.buf) {
                ql_dbg(ql_dbg_user, vha, 0x7005,
                    "Failed allocate request dma len=%x\n",
                    bsg_job->request_payload.payload_len);
                rval = -ENOMEM;
                SET_DID_STATUS(bsg_reply->result, DID_IMM_RETRY);
                goto done_free_sp;
        }

        sp->remap.rsp.len = bsg_job->reply_payload.payload_len;
        sp->remap.rsp.buf = dma_pool_alloc(ha->purex_dma_pool,
            GFP_KERNEL, &sp->remap.rsp.dma);
        if (!sp->remap.rsp.buf) {
                ql_dbg(ql_dbg_user, vha, 0x7006,
                    "Failed allocate response dma len=%x\n",
                    bsg_job->reply_payload.payload_len);
                rval = -ENOMEM;
                SET_DID_STATUS(bsg_reply->result, DID_IMM_RETRY);
                goto done_free_remap_req;
        }
        sg_copy_to_buffer(bsg_job->request_payload.sg_list,
            bsg_job->request_payload.sg_cnt, sp->remap.req.buf,
            sp->remap.req.len);
        sp->remap.remapped = true;

        sp->type = SRB_ELS_CMD_HST_NOLOGIN;
        sp->name = "SPCN_BSG_HST_NOLOGIN";
        sp->u.bsg_cmd.bsg_job = bsg_job;
        qla_parse_auth_els_ctl(sp);

        sp->free = qla2x00_bsg_sp_free;
        sp->done = qla2x00_bsg_job_done;

        cnt = 0;
retry:
        rval = qla2x00_start_sp(sp);
        switch (rval) {
        case QLA_SUCCESS:
                ql_dbg(ql_dbg_edif, vha, 0x700a,
                       "%s %s %8phN xchg %x ctlflag %x hdl %x reqlen %xh bsg ptr %p\n",
                       __func__, sc_to_str(p->e.sub_cmd), fcport->port_name,
                       p->e.extra_rx_xchg_address, p->e.extra_control_flags,
                       sp->handle, sp->remap.req.len, bsg_job);
                break;
        case -EAGAIN:
                msleep(EDIF_MSLEEP_INTERVAL);
                cnt++;
                if (cnt < EDIF_RETRY_COUNT)
                        goto retry;
                fallthrough;
        default:
                ql_log(ql_log_warn, vha, 0x700e,
                    "%s qla2x00_start_sp failed = %d\n", __func__, rval);
                SET_DID_STATUS(bsg_reply->result, DID_IMM_RETRY);
                rval = -EIO;
                goto done_free_remap_rsp;
        }
        return rval;

done_free_remap_rsp:
        dma_pool_free(ha->purex_dma_pool, sp->remap.rsp.buf,
            sp->remap.rsp.dma);
done_free_remap_req:
        dma_pool_free(ha->purex_dma_pool, sp->remap.req.buf,
            sp->remap.req.dma);
done_free_sp:
        qla2x00_rel_sp(sp);

done:
        return rval;
}

void qla_edif_sess_down(struct scsi_qla_host *vha, struct fc_port *sess)
{
        u16 cnt = 0;

        if (sess->edif.app_sess_online && DBELL_ACTIVE(vha)) {
                ql_dbg(ql_dbg_disc, vha, 0xf09c,
                        "%s: sess %8phN send port_offline event\n",
                        __func__, sess->port_name);
                sess->edif.app_sess_online = 0;
                sess->edif.sess_down_acked = 0;
                qla_edb_eventcreate(vha, VND_CMD_AUTH_STATE_SESSION_SHUTDOWN,
                    sess->d_id.b24, 0, sess);
                qla2x00_post_aen_work(vha, FCH_EVT_PORT_OFFLINE, sess->d_id.b24);

                while (!READ_ONCE(sess->edif.sess_down_acked) &&
                       !test_bit(VPORT_DELETE, &vha->dpc_flags)) {
                        msleep(100);
                        cnt++;
                        if (cnt > 100)
                                break;
                }
                sess->edif.sess_down_acked = 0;
                ql_dbg(ql_dbg_disc, vha, 0xf09c,
                       "%s: sess %8phN port_offline event completed\n",
                       __func__, sess->port_name);
        }
}

void qla_edif_clear_appdata(struct scsi_qla_host *vha, struct fc_port *fcport)
{
        if (!(fcport->flags & FCF_FCSP_DEVICE))
                return;

        qla_edb_clear(vha, fcport->d_id);
        qla_enode_clear(vha, fcport->d_id);
}