root/drivers/scsi/elx/efct/efct_scsi.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2021 Broadcom. All Rights Reserved. The term
 * “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
 */

#include "efct_driver.h"
#include "efct_hw.h"

#define enable_tsend_auto_resp(efct)    1
#define enable_treceive_auto_resp(efct) 0

#define SCSI_IOFMT "[%04x][i:%04x t:%04x h:%04x]"

#define scsi_io_printf(io, fmt, ...) \
        efc_log_debug(io->efct, "[%s]" SCSI_IOFMT fmt, \
                io->node->display_name, io->instance_index,\
                io->init_task_tag, io->tgt_task_tag, io->hw_tag, ##__VA_ARGS__)

#define EFCT_LOG_ENABLE_SCSI_TRACE(efct)                \
                (((efct) != NULL) ? (((efct)->logmask & (1U << 2)) != 0) : 0)

#define scsi_io_trace(io, fmt, ...) \
        do { \
                if (EFCT_LOG_ENABLE_SCSI_TRACE(io->efct)) \
                        scsi_io_printf(io, fmt, ##__VA_ARGS__); \
        } while (0)

struct efct_io *
efct_scsi_io_alloc(struct efct_node *node)
{
        struct efct *efct;
        struct efct_xport *xport;
        struct efct_io *io;
        unsigned long flags;

        efct = node->efct;

        xport = efct->xport;

        io = efct_io_pool_io_alloc(efct->xport->io_pool);
        if (!io) {
                efc_log_err(efct, "IO alloc Failed\n");
                atomic_add_return(1, &xport->io_alloc_failed_count);
                return NULL;
        }

        /* initialize refcount */
        kref_init(&io->ref);
        io->release = _efct_scsi_io_free;

        /* set generic fields */
        io->efct = efct;
        io->node = node;
        kref_get(&node->ref);

        /* set type and name */
        io->io_type = EFCT_IO_TYPE_IO;
        io->display_name = "scsi_io";

        io->cmd_ini = false;
        io->cmd_tgt = true;

        /* Add to node's active_ios list */
        INIT_LIST_HEAD(&io->list_entry);
        spin_lock_irqsave(&node->active_ios_lock, flags);
        list_add(&io->list_entry, &node->active_ios);

        spin_unlock_irqrestore(&node->active_ios_lock, flags);

        return io;
}

void
_efct_scsi_io_free(struct kref *arg)
{
        struct efct_io *io = container_of(arg, struct efct_io, ref);
        struct efct *efct = io->efct;
        struct efct_node *node = io->node;
        unsigned long flags = 0;

        scsi_io_trace(io, "freeing io 0x%p %s\n", io, io->display_name);

        if (io->io_free) {
                efc_log_err(efct, "IO already freed.\n");
                return;
        }

        spin_lock_irqsave(&node->active_ios_lock, flags);
        list_del_init(&io->list_entry);
        spin_unlock_irqrestore(&node->active_ios_lock, flags);

        kref_put(&node->ref, node->release);
        io->node = NULL;
        efct_io_pool_io_free(efct->xport->io_pool, io);
}

void
efct_scsi_io_free(struct efct_io *io)
{
        scsi_io_trace(io, "freeing io 0x%p %s\n", io, io->display_name);
        WARN_ON(!refcount_read(&io->ref.refcount));
        kref_put(&io->ref, io->release);
}

static void
efct_target_io_cb(struct efct_hw_io *hio, u32 length, int status,
                  u32 ext_status, void *app)
{
        u32 flags = 0;
        struct efct_io *io = app;
        struct efct *efct;
        enum efct_scsi_io_status scsi_stat = EFCT_SCSI_STATUS_GOOD;
        efct_scsi_io_cb_t cb;

        if (!io || !io->efct) {
                pr_err("%s: IO can not be NULL\n", __func__);
                return;
        }

        scsi_io_trace(io, "status x%x ext_status x%x\n", status, ext_status);

        efct = io->efct;

        io->transferred += length;

        if (!io->scsi_tgt_cb) {
                efct_scsi_check_pending(efct);
                return;
        }

        /* Call target server completion */
        cb = io->scsi_tgt_cb;

        /* Clear the callback before invoking the callback */
        io->scsi_tgt_cb = NULL;

        /* if status was good, and auto-good-response was set,
         * then callback target-server with IO_CMPL_RSP_SENT,
         * otherwise send IO_CMPL
         */
        if (status == 0 && io->auto_resp)
                flags |= EFCT_SCSI_IO_CMPL_RSP_SENT;
        else
                flags |= EFCT_SCSI_IO_CMPL;

        switch (status) {
        case SLI4_FC_WCQE_STATUS_SUCCESS:
                scsi_stat = EFCT_SCSI_STATUS_GOOD;
                break;
        case SLI4_FC_WCQE_STATUS_DI_ERROR:
                if (ext_status & SLI4_FC_DI_ERROR_GE)
                        scsi_stat = EFCT_SCSI_STATUS_DIF_GUARD_ERR;
                else if (ext_status & SLI4_FC_DI_ERROR_AE)
                        scsi_stat = EFCT_SCSI_STATUS_DIF_APP_TAG_ERROR;
                else if (ext_status & SLI4_FC_DI_ERROR_RE)
                        scsi_stat = EFCT_SCSI_STATUS_DIF_REF_TAG_ERROR;
                else
                        scsi_stat = EFCT_SCSI_STATUS_DIF_UNKNOWN_ERROR;
                break;
        case SLI4_FC_WCQE_STATUS_LOCAL_REJECT:
                switch (ext_status) {
                case SLI4_FC_LOCAL_REJECT_INVALID_RELOFFSET:
                case SLI4_FC_LOCAL_REJECT_ABORT_REQUESTED:
                        scsi_stat = EFCT_SCSI_STATUS_ABORTED;
                        break;
                case SLI4_FC_LOCAL_REJECT_INVALID_RPI:
                        scsi_stat = EFCT_SCSI_STATUS_NEXUS_LOST;
                        break;
                case SLI4_FC_LOCAL_REJECT_NO_XRI:
                        scsi_stat = EFCT_SCSI_STATUS_NO_IO;
                        break;
                default:
                        /*we have seen 0x0d(TX_DMA_FAILED err)*/
                        scsi_stat = EFCT_SCSI_STATUS_ERROR;
                        break;
                }
                break;

        case SLI4_FC_WCQE_STATUS_TARGET_WQE_TIMEOUT:
                /* target IO timed out */
                scsi_stat = EFCT_SCSI_STATUS_TIMEDOUT_AND_ABORTED;
                break;

        case SLI4_FC_WCQE_STATUS_SHUTDOWN:
                /* Target IO cancelled by HW */
                scsi_stat = EFCT_SCSI_STATUS_SHUTDOWN;
                break;

        default:
                scsi_stat = EFCT_SCSI_STATUS_ERROR;
                break;
        }

        cb(io, scsi_stat, flags, io->scsi_tgt_cb_arg);

        efct_scsi_check_pending(efct);
}

static int
efct_scsi_build_sgls(struct efct_hw *hw, struct efct_hw_io *hio,
                     struct efct_scsi_sgl *sgl, u32 sgl_count,
                     enum efct_hw_io_type type)
{
        int rc;
        u32 i;
        struct efct *efct = hw->os;

        /* Initialize HW SGL */
        rc = efct_hw_io_init_sges(hw, hio, type);
        if (rc) {
                efc_log_err(efct, "efct_hw_io_init_sges failed: %d\n", rc);
                return -EIO;
        }

        for (i = 0; i < sgl_count; i++) {
                /* Add data SGE */
                rc = efct_hw_io_add_sge(hw, hio, sgl[i].addr, sgl[i].len);
                if (rc) {
                        efc_log_err(efct, "add sge failed cnt=%d rc=%d\n",
                                    sgl_count, rc);
                        return rc;
                }
        }

        return 0;
}

static void efc_log_sgl(struct efct_io *io)
{
        struct efct_hw_io *hio = io->hio;
        struct sli4_sge *data = NULL;
        u32 *dword = NULL;
        u32 i;
        u32 n_sge;

        scsi_io_trace(io, "def_sgl at 0x%x 0x%08x\n",
                      upper_32_bits(hio->def_sgl.phys),
                      lower_32_bits(hio->def_sgl.phys));
        n_sge = (hio->sgl == &hio->def_sgl) ? hio->n_sge : hio->def_sgl_count;
        for (i = 0, data = hio->def_sgl.virt; i < n_sge; i++, data++) {
                dword = (u32 *)data;

                scsi_io_trace(io, "SGL %2d 0x%08x 0x%08x 0x%08x 0x%08x\n",
                              i, dword[0], dword[1], dword[2], dword[3]);

                if (dword[2] & (1U << 31))
                        break;
        }
}

static void
efct_scsi_check_pending_async_cb(struct efct_hw *hw, int status,
                                 u8 *mqe, void *arg)
{
        struct efct_io *io = arg;

        if (io) {
                efct_hw_done_t cb = io->hw_cb;

                if (!io->hw_cb)
                        return;

                io->hw_cb = NULL;
                (cb)(io->hio, 0, SLI4_FC_WCQE_STATUS_DISPATCH_ERROR, 0, io);
        }
}

static int
efct_scsi_io_dispatch_hw_io(struct efct_io *io, struct efct_hw_io *hio)
{
        int rc = 0;
        struct efct *efct = io->efct;

        /* Got a HW IO;
         * update ini/tgt_task_tag with HW IO info and dispatch
         */
        io->hio = hio;
        if (io->cmd_tgt)
                io->tgt_task_tag = hio->indicator;
        else if (io->cmd_ini)
                io->init_task_tag = hio->indicator;
        io->hw_tag = hio->reqtag;

        hio->eq = io->hw_priv;

        /* Copy WQ steering */
        switch (io->wq_steering) {
        case EFCT_SCSI_WQ_STEERING_CLASS >> EFCT_SCSI_WQ_STEERING_SHIFT:
                hio->wq_steering = EFCT_HW_WQ_STEERING_CLASS;
                break;
        case EFCT_SCSI_WQ_STEERING_REQUEST >> EFCT_SCSI_WQ_STEERING_SHIFT:
                hio->wq_steering = EFCT_HW_WQ_STEERING_REQUEST;
                break;
        case EFCT_SCSI_WQ_STEERING_CPU >> EFCT_SCSI_WQ_STEERING_SHIFT:
                hio->wq_steering = EFCT_HW_WQ_STEERING_CPU;
                break;
        }

        switch (io->io_type) {
        case EFCT_IO_TYPE_IO:
                rc = efct_scsi_build_sgls(&efct->hw, io->hio,
                                          io->sgl, io->sgl_count, io->hio_type);
                if (rc)
                        break;

                if (EFCT_LOG_ENABLE_SCSI_TRACE(efct))
                        efc_log_sgl(io);

                if (io->app_id)
                        io->iparam.fcp_tgt.app_id = io->app_id;

                io->iparam.fcp_tgt.vpi = io->node->vpi;
                io->iparam.fcp_tgt.rpi = io->node->rpi;
                io->iparam.fcp_tgt.s_id = io->node->port_fc_id;
                io->iparam.fcp_tgt.d_id = io->node->node_fc_id;
                io->iparam.fcp_tgt.xmit_len = io->wire_len;

                rc = efct_hw_io_send(&io->efct->hw, io->hio_type, io->hio,
                                     &io->iparam, io->hw_cb, io);
                break;
        default:
                scsi_io_printf(io, "Unknown IO type=%d\n", io->io_type);
                rc = -EIO;
                break;
        }
        return rc;
}

static int
efct_scsi_io_dispatch_no_hw_io(struct efct_io *io)
{
        int rc;

        switch (io->io_type) {
        case EFCT_IO_TYPE_ABORT: {
                struct efct_hw_io *hio_to_abort = NULL;

                hio_to_abort = io->io_to_abort->hio;

                if (!hio_to_abort) {
                        /*
                         * If "IO to abort" does not have an
                         * associated HW IO, immediately make callback with
                         * success. The command must have been sent to
                         * the backend, but the data phase has not yet
                         * started, so we don't have a HW IO.
                         *
                         * Note: since the backend shims should be
                         * taking a reference on io_to_abort, it should not
                         * be possible to have been completed and freed by
                         * the backend before the abort got here.
                         */
                        scsi_io_printf(io, "IO: not active\n");
                        ((efct_hw_done_t)io->hw_cb)(io->hio, 0,
                                        SLI4_FC_WCQE_STATUS_SUCCESS, 0, io);
                        rc = 0;
                        break;
                }

                /* HW IO is valid, abort it */
                scsi_io_printf(io, "aborting\n");
                rc = efct_hw_io_abort(&io->efct->hw, hio_to_abort,
                                      io->send_abts, io->hw_cb, io);
                if (rc) {
                        int status = SLI4_FC_WCQE_STATUS_SUCCESS;
                        efct_hw_done_t cb = io->hw_cb;

                        if (rc != -ENOENT && rc != -EINPROGRESS) {
                                status = -1;
                                scsi_io_printf(io, "Failed to abort IO rc=%d\n",
                                               rc);
                        }
                        cb(io->hio, 0, status, 0, io);
                        rc = 0;
                }

                break;
        }
        default:
                scsi_io_printf(io, "Unknown IO type=%d\n", io->io_type);
                rc = -EIO;
                break;
        }
        return rc;
}

static struct efct_io *
efct_scsi_dispatch_pending(struct efct *efct)
{
        struct efct_xport *xport = efct->xport;
        struct efct_io *io = NULL;
        struct efct_hw_io *hio;
        unsigned long flags = 0;
        int status;

        spin_lock_irqsave(&xport->io_pending_lock, flags);

        if (!list_empty(&xport->io_pending_list)) {
                io = list_first_entry(&xport->io_pending_list, struct efct_io,
                                      io_pending_link);
                list_del_init(&io->io_pending_link);
        }

        if (!io) {
                spin_unlock_irqrestore(&xport->io_pending_lock, flags);
                return NULL;
        }

        if (io->io_type == EFCT_IO_TYPE_ABORT) {
                hio = NULL;
        } else {
                hio = efct_hw_io_alloc(&efct->hw);
                if (!hio) {
                        /*
                         * No HW IO available.Put IO back on
                         * the front of pending list
                         */
                        list_add(&xport->io_pending_list, &io->io_pending_link);
                        io = NULL;
                } else {
                        hio->eq = io->hw_priv;
                }
        }

        /* Must drop the lock before dispatching the IO */
        spin_unlock_irqrestore(&xport->io_pending_lock, flags);

        if (!io)
                return NULL;

        /*
         * We pulled an IO off the pending list,
         * and either got an HW IO or don't need one
         */
        atomic_sub_return(1, &xport->io_pending_count);
        if (!hio)
                status = efct_scsi_io_dispatch_no_hw_io(io);
        else
                status = efct_scsi_io_dispatch_hw_io(io, hio);
        if (status) {
                /*
                 * Invoke the HW callback, but do so in the
                 * separate execution context,provided by the
                 * NOP mailbox completion processing context
                 * by using efct_hw_async_call()
                 */
                if (efct_hw_async_call(&efct->hw,
                                       efct_scsi_check_pending_async_cb, io)) {
                        efc_log_debug(efct, "call hw async failed\n");
                }
        }

        return io;
}

void
efct_scsi_check_pending(struct efct *efct)
{
        struct efct_xport *xport = efct->xport;
        struct efct_io *io = NULL;
        int count = 0;
        unsigned long flags = 0;
        int dispatch = 0;

        /* Guard against recursion */
        if (atomic_add_return(1, &xport->io_pending_recursing)) {
                /* This function is already running.  Decrement and return. */
                atomic_sub_return(1, &xport->io_pending_recursing);
                return;
        }

        while (efct_scsi_dispatch_pending(efct))
                count++;

        if (count) {
                atomic_sub_return(1, &xport->io_pending_recursing);
                return;
        }

        /*
         * If nothing was removed from the list,
         * we might be in a case where we need to abort an
         * active IO and the abort is on the pending list.
         * Look for an abort we can dispatch.
         */

        spin_lock_irqsave(&xport->io_pending_lock, flags);

        list_for_each_entry(io, &xport->io_pending_list, io_pending_link) {
                if (io->io_type == EFCT_IO_TYPE_ABORT && io->io_to_abort->hio) {
                        /* This IO has a HW IO, so it is
                         * active.  Dispatch the abort.
                         */
                        dispatch = 1;
                        list_del_init(&io->io_pending_link);
                        atomic_sub_return(1, &xport->io_pending_count);
                        break;
                }
        }

        spin_unlock_irqrestore(&xport->io_pending_lock, flags);

        if (dispatch) {
                if (efct_scsi_io_dispatch_no_hw_io(io)) {
                        if (efct_hw_async_call(&efct->hw,
                                efct_scsi_check_pending_async_cb, io)) {
                                efc_log_debug(efct, "hw async failed\n");
                        }
                }
        }

        atomic_sub_return(1, &xport->io_pending_recursing);
}

int
efct_scsi_io_dispatch(struct efct_io *io, void *cb)
{
        struct efct_hw_io *hio;
        struct efct *efct = io->efct;
        struct efct_xport *xport = efct->xport;
        unsigned long flags = 0;

        io->hw_cb = cb;

        /*
         * if this IO already has a HW IO, then this is either
         * not the first phase of the IO. Send it to the HW.
         */
        if (io->hio)
                return efct_scsi_io_dispatch_hw_io(io, io->hio);

        /*
         * We don't already have a HW IO associated with the IO. First check
         * the pending list. If not empty, add IO to the tail and process the
         * pending list.
         */
        spin_lock_irqsave(&xport->io_pending_lock, flags);
        if (!list_empty(&xport->io_pending_list)) {
                /*
                 * If this is a low latency request,
                 * the put at the front of the IO pending
                 * queue, otherwise put it at the end of the queue.
                 */
                if (io->low_latency) {
                        INIT_LIST_HEAD(&io->io_pending_link);
                        list_add(&xport->io_pending_list, &io->io_pending_link);
                } else {
                        INIT_LIST_HEAD(&io->io_pending_link);
                        list_add_tail(&io->io_pending_link,
                                      &xport->io_pending_list);
                }
                spin_unlock_irqrestore(&xport->io_pending_lock, flags);
                atomic_add_return(1, &xport->io_pending_count);
                atomic_add_return(1, &xport->io_total_pending);

                /* process pending list */
                efct_scsi_check_pending(efct);
                return 0;
        }
        spin_unlock_irqrestore(&xport->io_pending_lock, flags);

        /*
         * We don't have a HW IO associated with the IO and there's nothing
         * on the pending list. Attempt to allocate a HW IO and dispatch it.
         */
        hio = efct_hw_io_alloc(&io->efct->hw);
        if (!hio) {
                /* Couldn't get a HW IO. Save this IO on the pending list */
                spin_lock_irqsave(&xport->io_pending_lock, flags);
                INIT_LIST_HEAD(&io->io_pending_link);
                list_add_tail(&io->io_pending_link, &xport->io_pending_list);
                spin_unlock_irqrestore(&xport->io_pending_lock, flags);

                atomic_add_return(1, &xport->io_total_pending);
                atomic_add_return(1, &xport->io_pending_count);
                return 0;
        }

        /* We successfully allocated a HW IO; dispatch to HW */
        return efct_scsi_io_dispatch_hw_io(io, hio);
}

int
efct_scsi_io_dispatch_abort(struct efct_io *io, void *cb)
{
        struct efct *efct = io->efct;
        struct efct_xport *xport = efct->xport;
        unsigned long flags = 0;

        io->hw_cb = cb;

        /*
         * For aborts, we don't need a HW IO, but we still want
         * to pass through the pending list to preserve ordering.
         * Thus, if the pending list is not empty, add this abort
         * to the pending list and process the pending list.
         */
        spin_lock_irqsave(&xport->io_pending_lock, flags);
        if (!list_empty(&xport->io_pending_list)) {
                INIT_LIST_HEAD(&io->io_pending_link);
                list_add_tail(&io->io_pending_link, &xport->io_pending_list);
                spin_unlock_irqrestore(&xport->io_pending_lock, flags);
                atomic_add_return(1, &xport->io_pending_count);
                atomic_add_return(1, &xport->io_total_pending);

                /* process pending list */
                efct_scsi_check_pending(efct);
                return 0;
        }
        spin_unlock_irqrestore(&xport->io_pending_lock, flags);

        /* nothing on pending list, dispatch abort */
        return efct_scsi_io_dispatch_no_hw_io(io);
}

static inline int
efct_scsi_xfer_data(struct efct_io *io, u32 flags,
                    struct efct_scsi_sgl *sgl, u32 sgl_count, u64 xwire_len,
                    enum efct_hw_io_type type, int enable_ar,
                    efct_scsi_io_cb_t cb, void *arg)
{
        struct efct *efct;
        size_t residual = 0;

        io->sgl_count = sgl_count;

        efct = io->efct;

        scsi_io_trace(io, "%s wire_len %llu\n",
                      (type == EFCT_HW_IO_TARGET_READ) ? "send" : "recv",
                      xwire_len);

        io->hio_type = type;

        io->scsi_tgt_cb = cb;
        io->scsi_tgt_cb_arg = arg;

        residual = io->exp_xfer_len - io->transferred;
        io->wire_len = (xwire_len < residual) ? xwire_len : residual;
        residual = (xwire_len - io->wire_len);

        memset(&io->iparam, 0, sizeof(io->iparam));
        io->iparam.fcp_tgt.ox_id = io->init_task_tag;
        io->iparam.fcp_tgt.offset = io->transferred;
        io->iparam.fcp_tgt.cs_ctl = io->cs_ctl;
        io->iparam.fcp_tgt.timeout = io->timeout;

        /* if this is the last data phase and there is no residual, enable
         * auto-good-response
         */
        if (enable_ar && (flags & EFCT_SCSI_LAST_DATAPHASE) && residual == 0 &&
            ((io->transferred + io->wire_len) == io->exp_xfer_len) &&
            (!(flags & EFCT_SCSI_NO_AUTO_RESPONSE))) {
                io->iparam.fcp_tgt.flags |= SLI4_IO_AUTO_GOOD_RESPONSE;
                io->auto_resp = true;
        } else {
                io->auto_resp = false;
        }

        /* save this transfer length */
        io->xfer_req = io->wire_len;

        /* Adjust the transferred count to account for overrun
         * when the residual is calculated in efct_scsi_send_resp
         */
        io->transferred += residual;

        /* Adjust the SGL size if there is overrun */

        if (residual) {
                struct efct_scsi_sgl  *sgl_ptr = &io->sgl[sgl_count - 1];

                while (residual) {
                        size_t len = sgl_ptr->len;

                        if (len > residual) {
                                sgl_ptr->len = len - residual;
                                residual = 0;
                        } else {
                                sgl_ptr->len = 0;
                                residual -= len;
                                io->sgl_count--;
                        }
                        sgl_ptr--;
                }
        }

        /* Set latency and WQ steering */
        io->low_latency = (flags & EFCT_SCSI_LOW_LATENCY) != 0;
        io->wq_steering = (flags & EFCT_SCSI_WQ_STEERING_MASK) >>
                                EFCT_SCSI_WQ_STEERING_SHIFT;
        io->wq_class = (flags & EFCT_SCSI_WQ_CLASS_MASK) >>
                                EFCT_SCSI_WQ_CLASS_SHIFT;

        if (efct->xport) {
                struct efct_xport *xport = efct->xport;

                if (type == EFCT_HW_IO_TARGET_READ) {
                        xport->fcp_stats.input_requests++;
                        xport->fcp_stats.input_bytes += xwire_len;
                } else if (type == EFCT_HW_IO_TARGET_WRITE) {
                        xport->fcp_stats.output_requests++;
                        xport->fcp_stats.output_bytes += xwire_len;
                }
        }
        return efct_scsi_io_dispatch(io, efct_target_io_cb);
}

int
efct_scsi_send_rd_data(struct efct_io *io, u32 flags,
                       struct efct_scsi_sgl *sgl, u32 sgl_count, u64 len,
                       efct_scsi_io_cb_t cb, void *arg)
{
        return efct_scsi_xfer_data(io, flags, sgl, sgl_count,
                                   len, EFCT_HW_IO_TARGET_READ,
                                   enable_tsend_auto_resp(io->efct), cb, arg);
}

int
efct_scsi_recv_wr_data(struct efct_io *io, u32 flags,
                       struct efct_scsi_sgl *sgl, u32 sgl_count, u64 len,
                       efct_scsi_io_cb_t cb, void *arg)
{
        return efct_scsi_xfer_data(io, flags, sgl, sgl_count, len,
                                   EFCT_HW_IO_TARGET_WRITE,
                                   enable_treceive_auto_resp(io->efct), cb, arg);
}

int
efct_scsi_send_resp(struct efct_io *io, u32 flags,
                    struct efct_scsi_cmd_resp *rsp,
                    efct_scsi_io_cb_t cb, void *arg)
{
        struct efct *efct;
        int residual;
        /* Always try auto resp */
        bool auto_resp = true;
        u8 scsi_status = 0;
        u16 scsi_status_qualifier = 0;
        u8 *sense_data = NULL;
        u32 sense_data_length = 0;

        efct = io->efct;

        if (rsp) {
                scsi_status = rsp->scsi_status;
                scsi_status_qualifier = rsp->scsi_status_qualifier;
                sense_data = rsp->sense_data;
                sense_data_length = rsp->sense_data_length;
                residual = rsp->residual;
        } else {
                residual = io->exp_xfer_len - io->transferred;
        }

        io->wire_len = 0;
        io->hio_type = EFCT_HW_IO_TARGET_RSP;

        io->scsi_tgt_cb = cb;
        io->scsi_tgt_cb_arg = arg;

        memset(&io->iparam, 0, sizeof(io->iparam));
        io->iparam.fcp_tgt.ox_id = io->init_task_tag;
        io->iparam.fcp_tgt.offset = 0;
        io->iparam.fcp_tgt.cs_ctl = io->cs_ctl;
        io->iparam.fcp_tgt.timeout = io->timeout;

        /* Set low latency queueing request */
        io->low_latency = (flags & EFCT_SCSI_LOW_LATENCY) != 0;
        io->wq_steering = (flags & EFCT_SCSI_WQ_STEERING_MASK) >>
                                EFCT_SCSI_WQ_STEERING_SHIFT;
        io->wq_class = (flags & EFCT_SCSI_WQ_CLASS_MASK) >>
                                EFCT_SCSI_WQ_CLASS_SHIFT;

        if (scsi_status != 0 || residual || sense_data_length) {
                struct fcp_resp_with_ext *fcprsp = io->rspbuf.virt;
                u8 *sns_data;

                if (!fcprsp) {
                        efc_log_err(efct, "NULL response buffer\n");
                        return -EIO;
                }

                sns_data = (u8 *)io->rspbuf.virt + sizeof(*fcprsp);

                auto_resp = false;

                memset(fcprsp, 0, sizeof(*fcprsp));

                io->wire_len += sizeof(*fcprsp);

                fcprsp->resp.fr_status = scsi_status;
                fcprsp->resp.fr_retry_delay =
                        cpu_to_be16(scsi_status_qualifier);

                /* set residual status if necessary */
                if (residual != 0) {
                        /* FCP: if data transferred is less than the
                         * amount expected, then this is an underflow.
                         * If data transferred would have been greater
                         * than the amount expected this is an overflow
                         */
                        if (residual > 0) {
                                fcprsp->resp.fr_flags |= FCP_RESID_UNDER;
                                fcprsp->ext.fr_resid =  cpu_to_be32(residual);
                        } else {
                                fcprsp->resp.fr_flags |= FCP_RESID_OVER;
                                fcprsp->ext.fr_resid = cpu_to_be32(-residual);
                        }
                }

                if (EFCT_SCSI_SNS_BUF_VALID(sense_data) && sense_data_length) {
                        if (sense_data_length > SCSI_SENSE_BUFFERSIZE) {
                                efc_log_err(efct, "Sense exceeds max size.\n");
                                return -EIO;
                        }

                        fcprsp->resp.fr_flags |= FCP_SNS_LEN_VAL;
                        memcpy(sns_data, sense_data, sense_data_length);
                        fcprsp->ext.fr_sns_len = cpu_to_be32(sense_data_length);
                        io->wire_len += sense_data_length;
                }

                io->sgl[0].addr = io->rspbuf.phys;
                io->sgl[0].dif_addr = 0;
                io->sgl[0].len = io->wire_len;
                io->sgl_count = 1;
        }

        if (auto_resp)
                io->iparam.fcp_tgt.flags |= SLI4_IO_AUTO_GOOD_RESPONSE;

        return efct_scsi_io_dispatch(io, efct_target_io_cb);
}

static int
efct_target_bls_resp_cb(struct efct_hw_io *hio, u32 length, int status,
                        u32 ext_status, void *app)
{
        struct efct_io *io = app;
        struct efct *efct;
        enum efct_scsi_io_status bls_status;

        efct = io->efct;

        /* BLS isn't really a "SCSI" concept, but use SCSI status */
        if (status) {
                io_error_log(io, "s=%#x x=%#x\n", status, ext_status);
                bls_status = EFCT_SCSI_STATUS_ERROR;
        } else {
                bls_status = EFCT_SCSI_STATUS_GOOD;
        }

        if (io->bls_cb) {
                efct_scsi_io_cb_t bls_cb = io->bls_cb;
                void *bls_cb_arg = io->bls_cb_arg;

                io->bls_cb = NULL;
                io->bls_cb_arg = NULL;

                /* invoke callback */
                bls_cb(io, bls_status, 0, bls_cb_arg);
        }

        efct_scsi_check_pending(efct);
        return 0;
}

static int
efct_target_send_bls_resp(struct efct_io *io,
                          efct_scsi_io_cb_t cb, void *arg)
{
        struct efct_node *node = io->node;
        struct sli_bls_params *bls = &io->iparam.bls;
        struct efct *efct = node->efct;
        struct fc_ba_acc *acc;
        int rc;

        /* fill out IO structure with everything needed to send BA_ACC */
        memset(&io->iparam, 0, sizeof(io->iparam));
        bls->ox_id = io->init_task_tag;
        bls->rx_id = io->abort_rx_id;
        bls->vpi = io->node->vpi;
        bls->rpi = io->node->rpi;
        bls->s_id = U32_MAX;
        bls->d_id = io->node->node_fc_id;
        bls->rpi_registered = true;

        acc = (void *)bls->payload;
        acc->ba_ox_id = cpu_to_be16(bls->ox_id);
        acc->ba_rx_id = cpu_to_be16(bls->rx_id);
        acc->ba_high_seq_cnt = cpu_to_be16(U16_MAX);

        /* generic io fields have already been populated */

        /* set type and BLS-specific fields */
        io->io_type = EFCT_IO_TYPE_BLS_RESP;
        io->display_name = "bls_rsp";
        io->hio_type = EFCT_HW_BLS_ACC;
        io->bls_cb = cb;
        io->bls_cb_arg = arg;

        /* dispatch IO */
        rc = efct_hw_bls_send(efct, FC_RCTL_BA_ACC, bls,
                              efct_target_bls_resp_cb, io);
        return rc;
}

static int efct_bls_send_rjt_cb(struct efct_hw_io *hio, u32 length, int status,
                                u32 ext_status, void *app)
{
        struct efct_io *io = app;

        efct_scsi_io_free(io);
        return 0;
}

struct efct_io *
efct_bls_send_rjt(struct efct_io *io, struct fc_frame_header *hdr)
{
        struct efct_node *node = io->node;
        struct sli_bls_params *bls = &io->iparam.bls;
        struct efct *efct = node->efct;
        struct fc_ba_rjt *acc;
        int rc;

        /* fill out BLS Response-specific fields */
        io->io_type = EFCT_IO_TYPE_BLS_RESP;
        io->display_name = "ba_rjt";
        io->hio_type = EFCT_HW_BLS_RJT;
        io->init_task_tag = be16_to_cpu(hdr->fh_ox_id);

        /* fill out iparam fields */
        memset(&io->iparam, 0, sizeof(io->iparam));
        bls->ox_id = be16_to_cpu(hdr->fh_ox_id);
        bls->rx_id = be16_to_cpu(hdr->fh_rx_id);
        bls->vpi = io->node->vpi;
        bls->rpi = io->node->rpi;
        bls->s_id = U32_MAX;
        bls->d_id = io->node->node_fc_id;
        bls->rpi_registered = true;

        acc = (void *)bls->payload;
        acc->br_reason = ELS_RJT_UNAB;
        acc->br_explan = ELS_EXPL_NONE;

        rc = efct_hw_bls_send(efct, FC_RCTL_BA_RJT, bls, efct_bls_send_rjt_cb,
                              io);
        if (rc) {
                efc_log_err(efct, "efct_scsi_io_dispatch() failed: %d\n", rc);
                efct_scsi_io_free(io);
                io = NULL;
        }
        return io;
}

int
efct_scsi_send_tmf_resp(struct efct_io *io,
                        enum efct_scsi_tmf_resp rspcode,
                        u8 addl_rsp_info[3],
                        efct_scsi_io_cb_t cb, void *arg)
{
        int rc;
        struct {
                struct fcp_resp_with_ext rsp_ext;
                struct fcp_resp_rsp_info info;
        } *fcprsp;
        u8 fcp_rspcode;

        io->wire_len = 0;

        switch (rspcode) {
        case EFCT_SCSI_TMF_FUNCTION_COMPLETE:
                fcp_rspcode = FCP_TMF_CMPL;
                break;
        case EFCT_SCSI_TMF_FUNCTION_SUCCEEDED:
        case EFCT_SCSI_TMF_FUNCTION_IO_NOT_FOUND:
                fcp_rspcode = FCP_TMF_CMPL;
                break;
        case EFCT_SCSI_TMF_FUNCTION_REJECTED:
                fcp_rspcode = FCP_TMF_REJECTED;
                break;
        case EFCT_SCSI_TMF_INCORRECT_LOGICAL_UNIT_NUMBER:
                fcp_rspcode = FCP_TMF_INVALID_LUN;
                break;
        case EFCT_SCSI_TMF_SERVICE_DELIVERY:
                fcp_rspcode = FCP_TMF_FAILED;
                break;
        default:
                fcp_rspcode = FCP_TMF_REJECTED;
                break;
        }

        io->hio_type = EFCT_HW_IO_TARGET_RSP;

        io->scsi_tgt_cb = cb;
        io->scsi_tgt_cb_arg = arg;

        if (io->tmf_cmd == EFCT_SCSI_TMF_ABORT_TASK) {
                rc = efct_target_send_bls_resp(io, cb, arg);
                return rc;
        }

        /* populate the FCP TMF response */
        fcprsp = io->rspbuf.virt;
        memset(fcprsp, 0, sizeof(*fcprsp));

        fcprsp->rsp_ext.resp.fr_flags |= FCP_SNS_LEN_VAL;

        if (addl_rsp_info) {
                memcpy(fcprsp->info._fr_resvd, addl_rsp_info,
                       sizeof(fcprsp->info._fr_resvd));
        }
        fcprsp->info.rsp_code = fcp_rspcode;

        io->wire_len = sizeof(*fcprsp);

        fcprsp->rsp_ext.ext.fr_rsp_len =
                        cpu_to_be32(sizeof(struct fcp_resp_rsp_info));

        io->sgl[0].addr = io->rspbuf.phys;
        io->sgl[0].dif_addr = 0;
        io->sgl[0].len = io->wire_len;
        io->sgl_count = 1;

        memset(&io->iparam, 0, sizeof(io->iparam));
        io->iparam.fcp_tgt.ox_id = io->init_task_tag;
        io->iparam.fcp_tgt.offset = 0;
        io->iparam.fcp_tgt.cs_ctl = io->cs_ctl;
        io->iparam.fcp_tgt.timeout = io->timeout;

        rc = efct_scsi_io_dispatch(io, efct_target_io_cb);

        return rc;
}

static int
efct_target_abort_cb(struct efct_hw_io *hio, u32 length, int status,
                     u32 ext_status, void *app)
{
        struct efct_io *io = app;
        struct efct *efct;
        enum efct_scsi_io_status scsi_status;
        efct_scsi_io_cb_t abort_cb;
        void *abort_cb_arg;

        efct = io->efct;

        if (!io->abort_cb)
                goto done;

        abort_cb = io->abort_cb;
        abort_cb_arg = io->abort_cb_arg;

        io->abort_cb = NULL;
        io->abort_cb_arg = NULL;

        switch (status) {
        case SLI4_FC_WCQE_STATUS_SUCCESS:
                scsi_status = EFCT_SCSI_STATUS_GOOD;
                break;
        case SLI4_FC_WCQE_STATUS_LOCAL_REJECT:
                switch (ext_status) {
                case SLI4_FC_LOCAL_REJECT_NO_XRI:
                        scsi_status = EFCT_SCSI_STATUS_NO_IO;
                        break;
                case SLI4_FC_LOCAL_REJECT_ABORT_IN_PROGRESS:
                        scsi_status = EFCT_SCSI_STATUS_ABORT_IN_PROGRESS;
                        break;
                default:
                        /*we have seen 0x15 (abort in progress)*/
                        scsi_status = EFCT_SCSI_STATUS_ERROR;
                        break;
                }
                break;
        case SLI4_FC_WCQE_STATUS_FCP_RSP_FAILURE:
                scsi_status = EFCT_SCSI_STATUS_CHECK_RESPONSE;
                break;
        default:
                scsi_status = EFCT_SCSI_STATUS_ERROR;
                break;
        }
        /* invoke callback */
        abort_cb(io->io_to_abort, scsi_status, 0, abort_cb_arg);

done:
        /* done with IO to abort,efct_ref_get(): efct_scsi_tgt_abort_io() */
        kref_put(&io->io_to_abort->ref, io->io_to_abort->release);

        efct_io_pool_io_free(efct->xport->io_pool, io);

        efct_scsi_check_pending(efct);
        return 0;
}

int
efct_scsi_tgt_abort_io(struct efct_io *io, efct_scsi_io_cb_t cb, void *arg)
{
        struct efct *efct;
        struct efct_xport *xport;
        int rc;
        struct efct_io *abort_io = NULL;

        efct = io->efct;
        xport = efct->xport;

        /* take a reference on IO being aborted */
        if (kref_get_unless_zero(&io->ref) == 0) {
                /* command no longer active */
                scsi_io_printf(io, "command no longer active\n");
                return -EIO;
        }

        /*
         * allocate a new IO to send the abort request. Use efct_io_alloc()
         * directly, as we need an IO object that will not fail allocation
         * due to allocations being disabled (in efct_scsi_io_alloc())
         */
        abort_io = efct_io_pool_io_alloc(efct->xport->io_pool);
        if (!abort_io) {
                atomic_add_return(1, &xport->io_alloc_failed_count);
                kref_put(&io->ref, io->release);
                return -EIO;
        }

        /* Save the target server callback and argument */
        /* set generic fields */
        abort_io->cmd_tgt = true;
        abort_io->node = io->node;

        /* set type and abort-specific fields */
        abort_io->io_type = EFCT_IO_TYPE_ABORT;
        abort_io->display_name = "tgt_abort";
        abort_io->io_to_abort = io;
        abort_io->send_abts = false;
        abort_io->abort_cb = cb;
        abort_io->abort_cb_arg = arg;

        /* now dispatch IO */
        rc = efct_scsi_io_dispatch_abort(abort_io, efct_target_abort_cb);
        if (rc)
                kref_put(&io->ref, io->release);
        return rc;
}

void
efct_scsi_io_complete(struct efct_io *io)
{
        if (io->io_free) {
                efc_log_debug(io->efct, "completion for non-busy io tag 0x%x\n",
                              io->tag);
                return;
        }

        scsi_io_trace(io, "freeing io 0x%p %s\n", io, io->display_name);
        kref_put(&io->ref, io->release);
}