root/usr/src/uts/common/io/comstar/port/srpt/srpt_stp.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * SCSI Target Port I/F for Solaris SCSI RDMA Protocol Target (SRP)
 * port provider module for the COMSTAR framework.
 */

#include <sys/cpuvar.h>
#include <sys/types.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/sysmacros.h>
#include <sys/sdt.h>
#include <sys/taskq.h>
#include <sys/atomic.h>

#include <sys/stmf.h>
#include <sys/stmf_ioctl.h>
#include <sys/portif.h>

#include <sys/scsi/generic/persist.h>
#include <sys/ib/mgt/ibdma/ibdma.h>

#include "srp.h"
#include "srpt_impl.h"
#include "srpt_cm.h"
#include "srpt_ioc.h"
#include "srpt_ch.h"
#include "srpt_stp.h"

extern srpt_ctxt_t      *srpt_ctxt;
extern uint32_t         srpt_iu_size;

/*
 * STMF LPort Interface Prototypes
 */
static stmf_status_t srpt_stp_xfer_data(struct scsi_task *task,
        struct stmf_data_buf *dbuf, uint32_t ioflags);
stmf_status_t srpt_stp_send_status(struct scsi_task *task,
        uint32_t ioflags);
static void srpt_stp_task_free(struct scsi_task *task);
static stmf_status_t srpt_stp_abort(struct stmf_local_port *lport,
        int abort_cmd, void *arg, uint32_t flags);
static void srpt_stp_task_poll(struct scsi_task *task);
static void srpt_stp_ctl(struct stmf_local_port *lport,
        int cmd, void *arg);
static stmf_status_t srpt_stp_info(uint32_t cmd,
        struct stmf_local_port *lport, void *arg, uint8_t *buf,
        uint32_t *bufsizep);
static void srpt_stp_event_handler(struct stmf_local_port *lport,
        int eventid, void *arg, uint32_t flags);

static void srpt_format_login_rsp(srp_login_req_t *req,
        srp_login_rsp_t *rsp, uint8_t flags);
static void srpt_format_login_rej(srp_login_req_t *req,
        srp_login_rej_t *rej, uint32_t reason);

static scsi_devid_desc_t *srpt_stp_alloc_scsi_devid_desc(uint64_t guid);
static void srpt_stp_free_scsi_devid_desc(scsi_devid_desc_t *sdd);

extern uint16_t srpt_send_msg_depth;

/*
 * srpt_stp_start_srp() - Start SRP service
 *
 * Enable the SRP service for the specified SCSI Target Port.
 */
int
srpt_stp_start_srp(srpt_target_port_t *tgt)
{
        ibt_status_t            status;
        ibdma_status_t          dma_status;
        int                     port;
        srpt_ioc_t              *ioc;

        if (tgt == NULL) {
                SRPT_DPRINTF_L1("stp_start_srp, NULL SCSI target port");
                return (IBT_FAILURE);
        }

        if (tgt->tp_ioc == NULL) {
                SRPT_DPRINTF_L1("stp_start_srp, SCSI target port NULL"
                    " IOC pointer");
                return (IBT_FAILURE);
        }
        ioc = tgt->tp_ioc;

        SRPT_DPRINTF_L2("stp_start_srp, register SRP service for"
            " svc_id (%016llx)", (u_longlong_t)tgt->tp_ibt_svc_id);
        status = ibt_register_service(srpt_ctxt->sc_ibt_hdl,
            &tgt->tp_ibt_svc_desc, tgt->tp_ibt_svc_id, 1,
            &tgt->tp_ibt_svc_hdl, NULL);
        if (status != IBT_SUCCESS) {
                tgt->tp_ibt_svc_hdl = NULL;
                SRPT_DPRINTF_L1("stp_start_srp, SRP service creation err (%d)",
                    status);
                return (status);
        }

        /*
         * Bind the service associated with the SCSI target port to
         * each active port of the I/O Controller.
         */
        for (port = 0; port < ioc->ioc_attr.hca_nports; port++) {
                status = srpt_ioc_svc_bind(tgt, port+1);
                if (status != IBT_SUCCESS &&
                    status != IBT_HCA_PORT_NOT_ACTIVE) {
                        SRPT_DPRINTF_L1("start_srp, Unable to bind"
                            " service (%d)", status);
                        goto srp_start_err;
                }
        }

        /* don't online if we have no active ports */
        if (tgt->tp_num_active_ports == 0) {
                SRPT_DPRINTF_L2("start_srp, no ports active for svc_id %016llx",
                    (u_longlong_t)tgt->tp_ibt_svc_id);
                status = IBT_HCA_PORT_NOT_ACTIVE;
                goto srp_start_err;
        }

        tgt->tp_srp_enabled = 1;

        /*
         * Calculate the new I/O Controller profile and either update the
         * profile if previously registered or register it with the IB
         * Device Management Agent.
         */
        SRPT_DPRINTF_L3("start_srp, update I/O Controller profile (%016llx)",
            (u_longlong_t)ioc->ioc_guid);

        srpt_ioc_init_profile(ioc);
        if (ioc->ioc_ibdma_hdl == NULL) {
                ioc->ioc_ibdma_hdl =
                    srpt_ctxt->sc_ibdma_ops.ibdma_register(ioc->ioc_guid,
                    &ioc->ioc_profile, &ioc->ioc_svc);
                if (ioc->ioc_ibdma_hdl == NULL) {
                        SRPT_DPRINTF_L1("start_srp, Unable to register"
                            " I/O Profile for svc_id %016llx",
                            (u_longlong_t)tgt->tp_ibt_svc_id);
                        status = IBT_FAILURE;
                        goto srp_start_err;
                }
        } else {
                dma_status =
                    srpt_ctxt->sc_ibdma_ops.ibdma_update(ioc->ioc_ibdma_hdl,
                    &ioc->ioc_profile, &ioc->ioc_svc);
                if (dma_status != IBDMA_SUCCESS) {
                        SRPT_DPRINTF_L1("start_srp, Unable to update I/O"
                            " Profile for svc_id %016llxi (%d)",
                            (u_longlong_t)tgt->tp_ibt_svc_id, dma_status);
                        status = IBT_FAILURE;
                        goto srp_start_err;
                }
        }

        return (IBT_SUCCESS);

srp_start_err:
        tgt->tp_srp_enabled = 0;
        srpt_ioc_svc_unbind_all(tgt);
        tgt->tp_num_active_ports = 0;
        if (tgt->tp_ibt_svc_hdl != NULL) {
                (void) ibt_deregister_service(srpt_ctxt->sc_ibt_hdl,
                    tgt->tp_ibt_svc_hdl);
                tgt->tp_ibt_svc_hdl = NULL;
        }
        return (status);
}

/*
 * srpt_stp_stop_srp() - Stop SRP service.
 *
 * Disable the SRP service on the specified SCSI Target Port.
 */
void
srpt_stp_stop_srp(srpt_target_port_t *tgt)
{
        ibt_status_t            status;
        ibdma_status_t          dma_status;
        srpt_ioc_t              *ioc;
        srpt_channel_t          *ch;

        if (tgt == NULL) {
                SRPT_DPRINTF_L2("stp_stop_srp, NULL SCSI Target Port"
                    " specified");
                return;
        }

        if (tgt->tp_ioc == NULL) {
                SRPT_DPRINTF_L2("stp_stop_srp, bad Target, IOC NULL");
                return;
        }
        ioc = tgt->tp_ioc;

        /*
         * Update the I/O Controller profile to remove the SRP service
         * for this SCSI target port.
         */
        tgt->tp_srp_enabled = 0;

        if (ioc->ioc_ibdma_hdl != NULL) {
                SRPT_DPRINTF_L3("stp_stop_srp, update I/O Controller"
                    " profile (%016llx)", (u_longlong_t)ioc->ioc_guid);
                srpt_ioc_init_profile(ioc);

                if (ioc->ioc_profile.ioc_service_entries == 0) {
                        SRPT_DPRINTF_L3("stp_stop_srp, no services active"
                            " unregister IOC profile");
                        srpt_ctxt->sc_ibdma_ops.ibdma_unregister(
                            ioc->ioc_ibdma_hdl);
                        ioc->ioc_ibdma_hdl = NULL;
                } else {
                        dma_status = srpt_ctxt->sc_ibdma_ops.ibdma_update(
                            ioc->ioc_ibdma_hdl, &ioc->ioc_profile,
                            &ioc->ioc_svc);
                        if (dma_status != IBDMA_SUCCESS) {
                                SRPT_DPRINTF_L1("stp_stop_srp, Unable to"
                                    " update I/O Profile (%d)", dma_status);
                                return;
                        }
                }
        }

        /*
         * Unbind the SRP service associated with the SCSI target port
         * from all of the I/O Controller physical ports.
         */
        SRPT_DPRINTF_L2("stp_stop_srp, unbind and de-register service"
            "(%016llx)", (u_longlong_t)tgt->tp_ibt_svc_id);
        if (tgt->tp_ibt_svc_hdl != NULL) {
                srpt_ioc_svc_unbind_all(tgt);
        }

        if (tgt->tp_ibt_svc_hdl != NULL) {
                status = ibt_deregister_service(srpt_ctxt->sc_ibt_hdl,
                    tgt->tp_ibt_svc_hdl);
                if (status != IBT_SUCCESS) {
                        SRPT_DPRINTF_L1("stp_stop_srp, de-register service"
                            " error(%d)", status);
                }
                tgt->tp_ibt_svc_hdl = NULL;
        }

        /*
         * SRP service is now off-line for this SCSI Target Port.
         * We force a disconnect (i.e. SRP Target Logout) for any
         * active SRP logins.
         */
        mutex_enter(&tgt->tp_ch_list_lock);
        ch = list_head(&tgt->tp_ch_list);
        while (ch != NULL) {
                SRPT_DPRINTF_L3("stp_stop_srp, disconnect ch(%p)",
                    (void *)ch);
                srpt_ch_disconnect(ch);
                ch = list_next(&tgt->tp_ch_list, ch);
        }
        mutex_exit(&tgt->tp_ch_list_lock);

        /*
         * wait for all sessions to terminate before returning
         */
        mutex_enter(&tgt->tp_sess_list_lock);
        while (!list_is_empty(&tgt->tp_sess_list)) {
                cv_wait(&tgt->tp_sess_complete, &tgt->tp_sess_list_lock);
        }
        mutex_exit(&tgt->tp_sess_list_lock);
}

/*
 * srpt_stp_alloc_port() - Allocate SCSI Target Port
 */
srpt_target_port_t *
srpt_stp_alloc_port(srpt_ioc_t *ioc, ib_guid_t guid)
{
        stmf_status_t           status;
        srpt_target_port_t      *tgt;
        stmf_local_port_t       *lport;
        uint64_t                temp;

        if (ioc == NULL) {
                SRPT_DPRINTF_L1("stp_alloc_port, NULL I/O Controller");
                return (NULL);
        }

        SRPT_DPRINTF_L3("stp_alloc_port, allocate STMF local port");
        lport = stmf_alloc(STMF_STRUCT_STMF_LOCAL_PORT, sizeof (*tgt), 0);
        if (lport == NULL) {
                SRPT_DPRINTF_L1("tgt_alloc_port, stmf_alloc failed");
                return (NULL);
        }

        tgt = lport->lport_port_private;
        ASSERT(tgt != NULL);

        mutex_init(&tgt->tp_lock, NULL, MUTEX_DRIVER, NULL);

        mutex_init(&tgt->tp_ch_list_lock, NULL, MUTEX_DRIVER, NULL);
        cv_init(&tgt->tp_offline_complete, NULL, CV_DRIVER, NULL);
        list_create(&tgt->tp_ch_list, sizeof (srpt_channel_t),
            offsetof(srpt_channel_t, ch_stp_node));

        mutex_init(&tgt->tp_sess_list_lock, NULL, MUTEX_DRIVER, NULL);
        cv_init(&tgt->tp_sess_complete, NULL, CV_DRIVER, NULL);
        list_create(&tgt->tp_sess_list, sizeof (srpt_session_t),
            offsetof(srpt_session_t, ss_node));

        tgt->tp_state    = SRPT_TGT_STATE_OFFLINE;
        tgt->tp_drv_disabled  = 0;
        tgt->tp_srp_enabled   = 0;
        tgt->tp_lport    = lport;
        tgt->tp_ioc        = ioc;
        tgt->tp_ibt_svc_id = guid;
        tgt->tp_ibt_svc_desc.sd_handler = srpt_cm_hdlr;
        tgt->tp_ibt_svc_desc.sd_flags   = IBT_SRV_NO_FLAGS;
        temp = h2b64(tgt->tp_ibt_svc_id);
        bcopy(&temp, &tgt->tp_srp_port_id[0], 8);
        temp = h2b64(tgt->tp_ioc->ioc_guid);
        bcopy(&temp, &tgt->tp_srp_port_id[8], 8);

        tgt->tp_nports  = ioc->ioc_attr.hca_nports;
        tgt->tp_hw_port =
            kmem_zalloc(sizeof (srpt_hw_port_t) * tgt->tp_nports, KM_SLEEP);
        tgt->tp_num_active_ports = 0;
        tgt->tp_requested_state = SRPT_TGT_STATE_OFFLINE;

        tgt->tp_scsi_devid = srpt_stp_alloc_scsi_devid_desc(tgt->tp_ibt_svc_id);

        lport->lport_id = tgt->tp_scsi_devid;
        lport->lport_pp = srpt_ctxt->sc_pp;
        lport->lport_ds = ioc->ioc_stmf_ds;
        lport->lport_xfer_data  = &srpt_stp_xfer_data;
        lport->lport_send_status = &srpt_stp_send_status;
        lport->lport_task_free  = &srpt_stp_task_free;
        lport->lport_abort      = &srpt_stp_abort;
        lport->lport_abort_timeout = 300;       /* 5 minutes */
        lport->lport_task_poll  = &srpt_stp_task_poll;
        lport->lport_ctl        = &srpt_stp_ctl;
        lport->lport_info       = &srpt_stp_info;
        lport->lport_event_handler = &srpt_stp_event_handler;

        /* set up as alua participating port */
        stmf_set_port_alua(lport);

        SRPT_DPRINTF_L3("stp_alloc_port, register STMF LPORT");

retry_registration:
        status = stmf_register_local_port(lport);
        if (status == STMF_SUCCESS) {
                SRPT_DPRINTF_L3("stp_alloc_port, LPORT successfully"
                    " registered");
                return (tgt);
        }

        if (status == STMF_BUSY) {
                /*
                 * This is only done on an administrative thread of
                 * execution so it is ok to take a while.
                 */
                SRPT_DPRINTF_L3("stp_alloc_port, delaying");
                delay(2 * drv_usectohz(1000000));
                goto retry_registration;
        }
        SRPT_DPRINTF_L1("stp_alloc_port, STMF register local port err(0x%llx)",
            (u_longlong_t)status);

        SRPT_DPRINTF_L3("stp_alloc_port, free STMF local port");
        cv_destroy(&tgt->tp_offline_complete);
        mutex_destroy(&tgt->tp_ch_list_lock);
        mutex_destroy(&tgt->tp_lock);
        if (tgt->tp_hw_port) {
                kmem_free(tgt->tp_hw_port,
                    sizeof (srpt_hw_port_t) * tgt->tp_nports);
        }
        if (tgt->tp_scsi_devid) {
                srpt_stp_free_scsi_devid_desc(tgt->tp_scsi_devid);
        }

        stmf_free(lport);

        return (NULL);
}

/*
 * srpt_stp_free_port() - Free SCSI Target Port
 */
stmf_status_t
srpt_stp_free_port(srpt_target_port_t *tgt)
{
        ASSERT(tgt != NULL);
        ASSERT(list_is_empty(&tgt->tp_sess_list));
        ASSERT(list_is_empty(&tgt->tp_ch_list));

        list_destroy(&tgt->tp_ch_list);
        list_destroy(&tgt->tp_sess_list);

        cv_destroy(&tgt->tp_sess_complete);
        cv_destroy(&tgt->tp_offline_complete);

        mutex_destroy(&tgt->tp_sess_list_lock);
        mutex_destroy(&tgt->tp_ch_list_lock);
        mutex_destroy(&tgt->tp_lock);


        SRPT_DPRINTF_L3("stp_free_port, free STMF local port");
        if (tgt->tp_hw_port) {
                kmem_free(tgt->tp_hw_port,
                    sizeof (srpt_hw_port_t) * tgt->tp_nports);
        }

        if (tgt->tp_scsi_devid) {
                srpt_stp_free_scsi_devid_desc(tgt->tp_scsi_devid);
        }

        stmf_free(tgt->tp_lport);

        return (STMF_SUCCESS);
}

/*
 * srpt_stp_destroy_port()
 */
stmf_status_t
srpt_stp_destroy_port(srpt_target_port_t *tgt)
{
        stmf_status_t           status;
        stmf_change_status_t    cstatus;
        uint64_t                guid;

        ASSERT(tgt != NULL);
        ASSERT(tgt->tp_lport != NULL);

        SRPT_DPRINTF_L3("stp_destroy_port, de-register STMF LPORT");

        mutex_enter(&tgt->tp_lock);
        if (tgt->tp_drv_disabled != 0) {
                /* already being destroyed, get out now - should not happen */
                mutex_exit(&tgt->tp_lock);
                return (STMF_ALREADY);
        }

        tgt->tp_drv_disabled = 1;
        guid = tgt->tp_ioc->ioc_guid;
        mutex_exit(&tgt->tp_lock);

        SRPT_DPRINTF_L2("stp_destroy_port: unbind and de-register"
            " services for GUID(%016llx)", (u_longlong_t)guid);

        cstatus.st_completion_status = STMF_SUCCESS;
        cstatus.st_additional_info = NULL;

        status = stmf_ctl(STMF_CMD_LPORT_OFFLINE, tgt->tp_lport, &cstatus);

        /*
         * Wait for asynchronous target off-line operation
         * to complete and then deregister the target
         * port.
         */
        mutex_enter(&tgt->tp_lock);
        while (tgt->tp_state != SRPT_TGT_STATE_OFFLINE) {
                cv_wait(&tgt->tp_offline_complete, &tgt->tp_lock);
        }
        mutex_exit(&tgt->tp_lock);

        SRPT_DPRINTF_L3("stp_destroy_port: IOC (0x%016llx) Target"
            " SRP off-line complete", (u_longlong_t)guid);

        /* loop waiting for all I/O to drain */
        for (;;) {
                status = stmf_deregister_local_port(tgt->tp_lport);
                if (status == STMF_BUSY) {
                        delay(drv_usectohz(1000000));
                } else {
                        break;
                }
        }

        if (status == STMF_SUCCESS) {
                SRPT_DPRINTF_L3("stp_destroy_port, LPORT de-register"
                    " complete");
        } else {
                /*
                 * Something other than a BUSY error, this should not happen.
                 */
                SRPT_DPRINTF_L1(
                    "stp_destroy_port, de-register STMF error(0x%llx)",
                    (u_longlong_t)status);
        }

        return (status);
}

/*
 * srpt_stp_xfer_data()
 */
/* ARGSUSED */
static stmf_status_t
srpt_stp_xfer_data(struct scsi_task *task, struct stmf_data_buf *dbuf,
        uint32_t ioflags)
{
        srpt_iu_t               *iu;
        srpt_channel_t          *ch;
        srpt_ds_dbuf_t          *db;
        ibt_send_wr_t           wr;
        ibt_wr_ds_t             ds;
        ibt_status_t            status;
        uint32_t                xfer_len;
        uint32_t                xferred_len;
        uint32_t                rdma_len;
        uint32_t                base_offset;
        uint32_t                desc_offset;
        srp_direct_desc_t       *desc;

        SRPT_DPRINTF_L3("stp_xfer_data, invoked task (%p), dbuf (%p)",
            (void *)task, (void *)dbuf);
        iu = task->task_port_private;
        ASSERT(iu != NULL);
        ASSERT(iu->iu_ch != NULL);
        /*
         * We should use iu->iu_ch->ch_swqe_posted to throttle
         * send wqe posting. This is very unlikely because we limit
         * the maximum number of initiator descriptors per IU (impact
         * of fragmentation of intiator buffer space) but it could occur
         * if the back-end (STMF) were to use too many small buffers. In
         * that case we would want to return STMF_BUSY.
         */

        SRPT_DPRINTF_L4("stp_xfer_data, dbuf->db_flags (0x%x)",
            dbuf->db_flags);
        SRPT_DPRINTF_L4("stp_xfer_data, dbuf->db_data_size (%d)",
            dbuf->db_data_size);
        SRPT_DPRINTF_L4("stp_xfer_data, dbuf->db_relative_offset (%d)",
            dbuf->db_relative_offset);

        ASSERT((dbuf->db_flags & (DB_DIRECTION_TO_RPORT |
            DB_DIRECTION_FROM_RPORT)) != (DB_DIRECTION_TO_RPORT |
            DB_DIRECTION_FROM_RPORT));

        db = dbuf->db_port_private;

        /*
         * Check to see if request will overflow the remote buffer; if so
         * return a bad status and let STMF abort the task.
         */
        if ((dbuf->db_relative_offset + dbuf->db_data_size) >
            iu->iu_tot_xfer_len) {
                SRPT_DPRINTF_L2("stp_xfer_data, overflow of remote buffer");
                return (STMF_FAILURE);
        }

        db->db_iu       = iu;
        wr.wr_trans  = IBT_RC_SRV;
        wr.wr_opcode = (dbuf->db_flags & DB_DIRECTION_TO_RPORT) ?
            IBT_WRC_RDMAW : IBT_WRC_RDMAR;
        wr.wr_nds    = 1;
        wr.wr_sgl    = &ds;

        /*
         * We know that the data transfer is within the bounds described
         * by our list of remote buffer descriptors.  Find the starting
         * point based on the offset for the transfer, then perform the
         * RDMA operations required of this transfer.
         */
        base_offset = 0;
        desc = iu->iu_rdescs;

        while ((base_offset + desc->dd_len) < dbuf->db_relative_offset) {
                base_offset += desc->dd_len;
                desc++;
        }

        xfer_len    = dbuf->db_data_size;
        xferred_len = 0;
        desc_offset = dbuf->db_relative_offset - base_offset;

        ch = iu->iu_ch;

        /*
         * If the channel is no longer connected then return an
         * error and do not initiate I/O.  STMF should abort the
         * task.
         */
        rw_enter(&ch->ch_rwlock, RW_READER);

        if (iu->iu_ch->ch_state == SRPT_CHANNEL_DISCONNECTING) {
                rw_exit(&iu->iu_ch->ch_rwlock);
                return (STMF_FAILURE);
        }

        while (xfer_len > 0) {
                rdma_len = desc->dd_len - desc_offset;

                /*
                 * We only generate completion entries on the last IB
                 * operation associated with any STMF buffer.
                 */
                if (rdma_len >= xfer_len) {
                        rdma_len = xfer_len;
                        wr.wr_flags  = IBT_WR_SEND_SIGNAL;
                } else {
                        wr.wr_flags  = IBT_WR_NO_FLAGS;
                }

                wr.wr.rc.rcwr.rdma.rdma_raddr = desc->dd_vaddr + desc_offset;
                wr.wr.rc.rcwr.rdma.rdma_rkey  = desc->dd_hdl;
                ds.ds_va  = db->db_sge.ds_va + xferred_len;
                ds.ds_key = db->db_sge.ds_key;
                ds.ds_len = rdma_len;

                SRPT_DPRINTF_L4("stp_xfer_data, post RDMA operation");

                /*
                 * If this task is being aborted or has been aborted,
                 * do not post additional I/O.
                 */
                DTRACE_SRP_8(xfer__start, srpt_channel_t, ch,
                    ibt_wr_ds_t, &(db->db_sge), srpt_iu_t, iu,
                    ibt_send_wr_t, &wr, uint32_t, rdma_len,
                    uint32_t, xferred_len, uint32_t, desc_offset,
                    uint32_t, wr.wr_opcode == IBT_WRC_RDMAR ? 0 : 1);
                mutex_enter(&iu->iu_lock);
                if ((iu->iu_flags & (SRPT_IU_SRP_ABORTING |
                    SRPT_IU_STMF_ABORTING | SRPT_IU_ABORTED)) != 0) {
                        mutex_exit(&iu->iu_lock);
                        rw_exit(&iu->iu_ch->ch_rwlock);
                        return (STMF_SUCCESS);
                }

                /*
                 * If a non-error CQE will be requested, add a reference to
                 * the IU and initialize the work request appropriately.
                 */
                if ((wr.wr_flags & IBT_WR_SEND_SIGNAL) != 0) {
                        wr.wr_id = srpt_ch_alloc_swqe_wrid(ch,
                            SRPT_SWQE_TYPE_DATA, (void *)dbuf);
                        if (wr.wr_id == 0) {
                                rw_exit(&iu->iu_ch->ch_rwlock);
                                mutex_exit(&iu->iu_lock);
                                return (STMF_BUSY);
                        }
                        atomic_inc_32(&iu->iu_sq_posted_cnt);
                } else {
                        wr.wr_id = 0;
                }

                status = ibt_post_send(iu->iu_ch->ch_chan_hdl, &wr, 1, NULL);
                mutex_exit(&iu->iu_lock);

                if (status != IBT_SUCCESS) {
                        /*
                         * Could not post to IB transport, report to STMF and
                         * and let it initiate an abort of the task.
                         */
                        SRPT_DPRINTF_L2("stp_xfer_data, post RDMA"
                            " error (%d)", status);

                        if ((wr.wr_flags & IBT_WR_SEND_SIGNAL) != 0) {
                                srpt_ch_free_swqe_wrid(ch, wr.wr_id);
                                atomic_dec_32(&iu->iu_sq_posted_cnt);
                        }
                        rw_exit(&iu->iu_ch->ch_rwlock);
                        return (STMF_FAILURE);
                }
                xferred_len += rdma_len;
                xfer_len    -= rdma_len;
                desc_offset  = 0;
                desc++;
        }

        rw_exit(&ch->ch_rwlock);
        return (STMF_SUCCESS);
}

/*
 * srpt_stp_send_mgmt_response() - Return SRP task managment response IU
 */
ibt_status_t
srpt_stp_send_mgmt_response(srpt_iu_t *iu, uint8_t srp_rsp,
        uint_t fence)
{
        srp_rsp_t       *rsp;
        srp_rsp_data_t  *data;
        uint32_t        rsp_length;
        ibt_status_t    status;
        uint8_t         *bufp;

        ASSERT(mutex_owned(&iu->iu_lock));
        rsp = iu->iu_buf;
        bufp = (uint8_t *)iu->iu_buf + SRP_RSP_SIZE;
        bzero(rsp, SRP_RSP_SIZE + sizeof (srp_rsp_data_t));
        rsp->rsp_type = SRP_IU_RSP;

        /*
         * Report ULP credits we have added since last response sent
         * over this channel.
         */
        rsp->rsp_req_limit_delta =
            h2b32(atomic_swap_32(&iu->iu_ch->ch_req_lim_delta, 0));
        rsp->rsp_tag = iu->iu_tag;

        /* srp_rsp_t is padded out, so use explicit size here */
        rsp_length = SRP_RSP_SIZE;
        if (srp_rsp != SRP_TM_SUCCESS) {
                rsp->rsp_flags |= SRP_RSP_VALID;
                data = (srp_rsp_data_t *)bufp;
                data->rd_rsp_status = srp_rsp;
                rsp->rsp_data_len = h2b32(sizeof (srp_rsp_data_t));
                rsp_length += sizeof (srp_rsp_data_t);
        }

        SRPT_DPRINTF_L4("stp_send_mgmt_response, sending on ch(%p),"
            " iu(%p), mgmt status(%d)", (void *)iu->iu_ch,
            (void *)iu, srp_rsp);

        DTRACE_SRP_4(task__response, srpt_channel_t, iu->iu_ch,
            srp_rsp_t, iu->iu_buf, scsi_task_t, iu->iu_stmf_task,
            int8_t, srp_rsp);

        status = srpt_ch_post_send(iu->iu_ch, iu, rsp_length, fence);
        if (status != IBT_SUCCESS) {
                SRPT_DPRINTF_L2("stp_send_mgmt_response, post "
                    "response err(%d)", status);
        }
        return (status);
}

/*
 * srpt_stp_send_response() - Send SRP command response IU
 */
ibt_status_t
srpt_stp_send_response(srpt_iu_t *iu, uint8_t scsi_status,
        uint8_t flags, uint32_t resid, uint16_t sense_length,
        uint8_t *sense_data, uint_t fence)
{
        srp_rsp_t       *rsp;
        uint32_t        rsp_length;
        uint8_t         *bufp;
        ibt_status_t    status;

        ASSERT(mutex_owned(&iu->iu_lock));
        rsp = iu->iu_buf;
        bufp = (uint8_t *)iu->iu_buf + SRP_RSP_SIZE;
        bzero(rsp, SRP_RSP_SIZE);
        rsp->rsp_type = SRP_IU_RSP;

        /*
         * Report ULP credits we have added since last response sent
         * over this channel.
         */
        rsp->rsp_req_limit_delta =
            h2b32(atomic_swap_32(&iu->iu_ch->ch_req_lim_delta, 0));
        rsp->rsp_tag = iu->iu_tag;
        rsp->rsp_status = scsi_status;

        rsp_length = SRP_RSP_SIZE;

        if (resid != 0) {
                rsp->rsp_flags |= flags;

                if ((flags & SRP_RSP_DO_OVER) ||
                    (flags & SRP_RSP_DO_UNDER)) {
                        rsp->rsp_do_resid_cnt = h2b32(resid);
                } else if ((flags & SRP_RSP_DI_OVER) ||
                    (flags & SRP_RSP_DI_UNDER)) {
                        rsp->rsp_di_resid_cnt = h2b32(resid);
                }
        }

        if (sense_length != 0) {
                rsp->rsp_flags |= SRP_RSP_SNS_VALID;
                if (SRP_RSP_SIZE + sense_length >
                    iu->iu_ch->ch_ti_iu_len) {
                        sense_length = iu->iu_ch->ch_ti_iu_len -
                            SRP_RSP_SIZE;
                }
                bcopy(sense_data, bufp, sense_length);
                rsp->rsp_sense_data_len = h2b32(sense_length);
                rsp_length += sense_length;
        }

        SRPT_DPRINTF_L4("stp_send_reponse, sending on ch(%p),"
            " iu(%p), length(%d)", (void *)iu->iu_ch,
            (void *)iu, rsp_length);

        DTRACE_SRP_4(task__response, srpt_channel_t, iu->iu_ch,
            srp_rsp_t, iu->iu_buf, scsi_task_t, iu->iu_stmf_task,
            uint8_t, scsi_status);

        status = srpt_ch_post_send(iu->iu_ch, iu, rsp_length, fence);
        if (status != IBT_SUCCESS) {
                SRPT_DPRINTF_L2("stp_send_response, post response err(%d)",
                    status);
        }
        return (status);
}

/*
 * srpt_stp_send_status()
 */
/* ARGSUSED */
stmf_status_t
srpt_stp_send_status(struct scsi_task *task, uint32_t ioflags)
{
        srpt_iu_t       *iu;
        ibt_status_t    status;

        ASSERT(task != NULL);
        iu = task->task_port_private;

        ASSERT(iu != NULL);

        mutex_enter(&iu->iu_lock);

        ASSERT(iu->iu_ch != NULL);

        SRPT_DPRINTF_L3("stp_send_status, invoked task (%p)"
            ", task_completion_status (%d)"
            ", task_resid (%d)"
            ", task_status_ctrl (%d)"
            ", task_scsi_status (%d)"
            ", task_sense_length (%d)"
            ", task_sense_data (%p)",
            (void *)task,
            (int)task->task_completion_status,
            task->task_resid,
            task->task_status_ctrl,
            task->task_scsi_status,
            task->task_sense_length,
            (void *)task->task_sense_data);

        DTRACE_SRP_4(scsi__response, srpt_channel_t, iu->iu_ch,
            srp_rsp_t, iu->iu_buf, scsi_task_t, task,
            int8_t, task->task_scsi_status);

        if ((iu->iu_flags & (SRPT_IU_STMF_ABORTING |
            SRPT_IU_SRP_ABORTING | SRPT_IU_ABORTED)) != 0) {
                mutex_exit(&iu->iu_lock);
                return (STMF_FAILURE);
        }

        /*
         * Indicate future aborts can not be initiated (although
         * we will handle any that have been requested since the
         * last I/O completed and before we are sending status).
         */
        iu->iu_flags |= SRPT_IU_RESP_SENT;

        /*
         * Send SRP command response or SRP task mgmt response.
         */
        if (task->task_mgmt_function == 0) {
                uint8_t         rsp_flags = 0;
                uint32_t        resbytes = 0;

                if (task->task_status_ctrl == TASK_SCTRL_OVER) {
                        resbytes = task->task_resid;

                        if (task->task_flags & TF_READ_DATA) {
                                SRPT_DPRINTF_L3(
                                    "stp_send_status, data out overrun");
                                rsp_flags |= SRP_RSP_DO_OVER;
                        } else if (task->task_flags & TF_WRITE_DATA) {
                                SRPT_DPRINTF_L3(
                                    "stp_send_status, data in overrun");
                                rsp_flags |= SRP_RSP_DI_OVER;
                        }
                } else if (task->task_status_ctrl == TASK_SCTRL_UNDER) {
                        resbytes = task->task_resid;

                        if (task->task_flags & TF_READ_DATA) {
                                SRPT_DPRINTF_L3(
                                    "stp_send_status, data out underrun");
                                rsp_flags |= SRP_RSP_DO_UNDER;
                        } else if (task->task_flags & TF_WRITE_DATA) {
                                SRPT_DPRINTF_L3(
                                    "stp_send_status, data in underrun");
                                rsp_flags |= SRP_RSP_DI_UNDER;
                        }
                }

                status = srpt_stp_send_response(iu,
                    task->task_scsi_status, rsp_flags, resbytes,
                    task->task_sense_length, task->task_sense_data, 0);
        } else {
                status = srpt_stp_send_mgmt_response(iu,
                    (task->task_scsi_status ?
                    SRP_TM_FAILED : SRP_TM_SUCCESS),
                    SRPT_FENCE_SEND);
        }

        /*
         * If we have an error posting the response return bad status
         * to STMF and let it initiate an abort for the task.
         */
        if (status != IBT_SUCCESS) {
                SRPT_DPRINTF_L2("stp_send_status, post response err(%d)",
                    status);

                /* clear the response sent flag since it never went out */
                iu->iu_flags &= ~SRPT_IU_RESP_SENT;

                mutex_exit(&iu->iu_lock);
                return (STMF_FAILURE);
        }
        mutex_exit(&iu->iu_lock);
        return (STMF_SUCCESS);
}

/*
 * srpt_stp_task_free() - STMF call-back.
 */
static void
srpt_stp_task_free(struct scsi_task *task)
{
        srpt_iu_t       *iu;
        srpt_channel_t  *ch;

        SRPT_DPRINTF_L3("stp_task_free, invoked task (%p)",
            (void *)task);

        iu = task->task_port_private;
        ASSERT(iu != NULL);

        mutex_enter(&iu->iu_lock);
        ch = iu->iu_ch;
        mutex_exit(&iu->iu_lock);

        ASSERT(ch != NULL);
        ASSERT(ch->ch_session != NULL);

        /*
         * Do not hold IU lock while task is being removed from
         * the session list - possible deadlock if cleaning up
         * channel when this is called.
         */
        srpt_stp_remove_task(ch->ch_session, iu);

        mutex_enter(&iu->iu_lock);
        iu->iu_stmf_task = NULL;

        srpt_ioc_repost_recv_iu(iu->iu_ioc, iu);

        mutex_exit(&iu->iu_lock);

        srpt_ch_release_ref(ch, 0);
}

/*
 * srpt_stp_abort() - STMF call-back.
 */
/* ARGSUSED */
static stmf_status_t
srpt_stp_abort(struct stmf_local_port *lport, int abort_cmd,
        void *arg, uint32_t flags)
{
        struct scsi_task        *task;
        srpt_iu_t               *iu;
        stmf_status_t           status;

        SRPT_DPRINTF_L3("stp_abort, invoked lport (%p), arg (%p)",
            (void *)lport, (void *)arg);

        task = (struct scsi_task *)arg;
        ASSERT(task != NULL);

        iu = (srpt_iu_t *)task->task_port_private;
        ASSERT(iu != NULL);

        mutex_enter(&iu->iu_lock);

        /*
         * If no I/O is outstanding then immediately transition to
         * aborted state.  If any I/O is in progress OR we've sent the
         * completion response, then indicate that an STMF abort has been
         * requested and ask STMF to call us back later to complete the abort.
         */
        if ((iu->iu_flags & SRPT_IU_RESP_SENT) ||
            (iu->iu_sq_posted_cnt > 0)) {
                SRPT_DPRINTF_L3("stp_abort, deferring abort request. "
                    "%d outstanding I/O for IU %p",
                    iu->iu_sq_posted_cnt, (void *)iu);
                iu->iu_flags |= SRPT_IU_STMF_ABORTING;
                status = STMF_BUSY;
        } else {
                SRPT_DPRINTF_L3("stp_abort, no outstanding I/O for %p",
                    (void *)iu);
                iu->iu_flags |= SRPT_IU_ABORTED;
                /* Synchronous abort - STMF will call task_free */
                status = STMF_ABORT_SUCCESS;
        }

        mutex_exit(&iu->iu_lock);
        return (status);
}

/*
 * srpt_stp_task_poll() - STMF call-back
 */
static void
srpt_stp_task_poll(struct scsi_task *task)
{
        SRPT_DPRINTF_L3("stp_task_poll, invoked, task (%p)",
            (void *)task);
}

/*
 * srpt_stp_ctl() - STMF call-back
 */
static void
srpt_stp_ctl(struct stmf_local_port *lport, int cmd, void *arg)
{
        stmf_state_change_info_t        *sc_info = arg;
        stmf_change_status_t            cstatus;
        stmf_status_t                   status;
        srpt_target_port_t              *tgt;
        char                            *why;

        ASSERT(sc_info != NULL);
        ASSERT(lport != NULL);

        tgt = lport->lport_port_private;
        ASSERT(tgt->tp_ioc != NULL);

        why = sc_info->st_additional_info;
        if (why == NULL) {
                why = "<null>";
        }

        SRPT_DPRINTF_L2("stp_ctl, invoked for LPORT (0x%016llx), cmd (%d), "
            "info (%s)", (u_longlong_t)tgt->tp_ibt_svc_id, cmd, why);

        cstatus.st_completion_status = STMF_SUCCESS;
        cstatus.st_additional_info = NULL;

        switch (cmd) {
        case STMF_CMD_LPORT_ONLINE:
                SRPT_DPRINTF_L2("stp_ctl, LPORT_ONLINE command,"
                    " st_rflags(0x%llx)", (u_longlong_t)sc_info->st_rflags);
                /*
                 * If the SCSI Target Port is not enabled by the driver,
                 * don't start and instead return busy.  This is a
                 * creation/destruction transitional state and the will
                 * either go away or become enabled.
                 */
                mutex_enter(&tgt->tp_lock);

                tgt->tp_requested_state = SRPT_TGT_STATE_ONLINE;

                if (tgt->tp_drv_disabled != 0) {
                        SRPT_DPRINTF_L1("stp_ctl, set LPORT_ONLINE failed - "
                            "LPORT (0x%016llx) BUSY",
                            (u_longlong_t)tgt->tp_ibt_svc_id);
                        cstatus.st_completion_status = STMF_BUSY;
                } else if ((tgt->tp_state == SRPT_TGT_STATE_ONLINE) ||
                    (tgt->tp_state == SRPT_TGT_STATE_ONLINING)) {
                        cstatus.st_completion_status = STMF_ALREADY;
                } else if (tgt->tp_state != SRPT_TGT_STATE_OFFLINE) {
                        cstatus.st_completion_status = STMF_INVALID_ARG;
                } else {
                        tgt->tp_state = SRPT_TGT_STATE_ONLINING;
                        status = srpt_stp_start_srp(tgt);
                        if (status != IBT_SUCCESS) {
                                tgt->tp_state = SRPT_TGT_STATE_OFFLINE;
                                cstatus.st_completion_status = STMF_INVALID_ARG;
                                if (tgt->tp_num_active_ports == 0) {
                                        SRPT_DPRINTF_L1(
                                            "stp_ctl, no ports active "
                                            "for HCA 0x%016llx. Target will "
                                            "not be placed online.",
                                            (u_longlong_t)tgt->tp_ibt_svc_id);
                                }
                        }
                }
                mutex_exit(&tgt->tp_lock);
                SRPT_DPRINTF_L3("stp_ctl, (0x%016llx) LPORT_ONLINE command"
                    " status (0x%llx)", (u_longlong_t)tgt->tp_ibt_svc_id,
                    (u_longlong_t)cstatus.st_completion_status);
                status = stmf_ctl(STMF_CMD_LPORT_ONLINE_COMPLETE, lport,
                    &cstatus);
                if (status != STMF_SUCCESS) {
                        SRPT_DPRINTF_L1("stp_ctl, ONLINE_COMPLETE returned"
                            " error(0x%llx)", (u_longlong_t)status);
                }
                break;

        case STMF_CMD_LPORT_OFFLINE:
                SRPT_DPRINTF_L2("stp_ctl, LPORT_OFFLINE command,"
                    " st_rflags(0x%llx)", (u_longlong_t)sc_info->st_rflags);
                mutex_enter(&tgt->tp_lock);

                /*
                 * Only keep persistent state if explicitly requested by user
                 * action, such as stmfadm offline-target or
                 * svcadm disable stmf.
                 * If not requested by the user, this was likely triggered by
                 * not having any HCA ports active.
                 */
                if (sc_info->st_rflags & STMF_RFLAG_USER_REQUEST) {
                        tgt->tp_requested_state = SRPT_TGT_STATE_OFFLINE;
                }

                if ((tgt->tp_state == SRPT_TGT_STATE_OFFLINE) ||
                    (tgt->tp_state == SRPT_TGT_STATE_OFFLINING)) {
                        cstatus.st_completion_status = STMF_ALREADY;
                } else if (tgt->tp_state != SRPT_TGT_STATE_ONLINE) {
                        cstatus.st_completion_status = STMF_INVALID_ARG;
                } else {
                        tgt->tp_state = SRPT_TGT_STATE_OFFLINING;
                        srpt_stp_stop_srp(tgt);
                }
                mutex_exit(&tgt->tp_lock);
                SRPT_DPRINTF_L3("stp_ctl, notify STMF OFFLINE complete"
                    " (0x%016llx)", (u_longlong_t)tgt->tp_ibt_svc_id);
                status = stmf_ctl(STMF_CMD_LPORT_OFFLINE_COMPLETE,
                    lport, &cstatus);
                if (status != STMF_SUCCESS) {
                        SRPT_DPRINTF_L1("stp_ctl, OFFLINE_COMPLETE returned"
                            " error(0x%llx)", (u_longlong_t)status);
                }
                break;

        case STMF_ACK_LPORT_ONLINE_COMPLETE:
                SRPT_DPRINTF_L2("stp_ctl, LPORT_ONLINE_COMPLETE ACK from"
                    " STMF");
                mutex_enter(&tgt->tp_lock);
                if (tgt->tp_state == SRPT_TGT_STATE_ONLINING) {
                        SRPT_DPRINTF_L2("stp_ctl, LPORT is ONLINE");
                        tgt->tp_state = SRPT_TGT_STATE_ONLINE;
                } else {
                        SRPT_DPRINTF_L2("stp_ctl, LPORT not on-lining");
                }
                mutex_exit(&tgt->tp_lock);
                break;

        case STMF_ACK_LPORT_OFFLINE_COMPLETE:
                SRPT_DPRINTF_L2("stp_ctl, LPORT_OFFLINE_COMPLETE ACK from"
                    " STMF");
                mutex_enter(&tgt->tp_lock);
                if (tgt->tp_state == SRPT_TGT_STATE_OFFLINING) {
                        SRPT_DPRINTF_L2("stp_ctl, LPORT is OFFLINE");
                        tgt->tp_state = SRPT_TGT_STATE_OFFLINE;
                        cv_broadcast(&tgt->tp_offline_complete);
                } else {
                        SRPT_DPRINTF_L2("stp_ctl, LPORT not off-lining");
                }
                mutex_exit(&tgt->tp_lock);
                break;

        default:
                SRPT_DPRINTF_L2("stp_ctl, cmd (%d) not handled",
                    cmd);
                break;
        }
}

/*
 * srpt_stp_info() - STMF call-back
 */
/* ARGSUSED */
static stmf_status_t
srpt_stp_info(uint32_t cmd, struct stmf_local_port *lport,
        void *arg, uint8_t *buf, uint32_t *bufsizep)
{
        SRPT_DPRINTF_L3("stp_info, invoked");
        return (STMF_SUCCESS);
}

/*
 * srpt_stp_event_handler() - STMF call-back
 */
/* ARGSUSED */
static void
srpt_stp_event_handler(struct stmf_local_port *lport, int eventid,
        void *arg, uint32_t flags)
{
        SRPT_DPRINTF_L3("stp_event_handler, invoked");
}

/*
 * srpt_stp_alloc_scsi_devid_desc()
 *
 * Allocate and initialize a SCSI device ID descriptor for
 * the SRP protocol.  Names are eui.GUID format.
 *
 * Both extension and guid are passed in host order.
 */
static scsi_devid_desc_t *
srpt_stp_alloc_scsi_devid_desc(uint64_t guid)
{
        scsi_devid_desc_t       *sdd;

        sdd = kmem_zalloc(sizeof (*sdd) + SRPT_EUI_ID_LEN + 1, KM_SLEEP);
        sdd->protocol_id = PROTOCOL_SRP;
        sdd->piv = 1;
        sdd->code_set = CODE_SET_ASCII;
        sdd->association = ID_IS_TARGET_PORT;
        sdd->ident_length = SRPT_EUI_ID_LEN;
        (void) sprintf((char *)sdd->ident, "eui.%016llX", (u_longlong_t)guid);
        return (sdd);
}

/*
 * srpt_stp_free_scsi_devid_desc()
 *
 * Free a SRPT SCSI device ID descriptor previously allocated via
 * srpt_stp_alloc_scsi_devid_desc().
 */
static void
srpt_stp_free_scsi_devid_desc(scsi_devid_desc_t *sdd)
{
        kmem_free(sdd, sizeof (*sdd) + SRPT_EUI_ID_LEN + 1);
}

/*
 * srpt_stp_alloc_session()
 */
srpt_session_t *
srpt_stp_alloc_session(srpt_target_port_t *tgt,
        uint8_t *i_id, uint8_t *t_id, uint8_t port,
        char *local_gid, char *remote_gid)
{
        stmf_status_t           status;
        srpt_session_t          *ss;
        stmf_scsi_session_t     *stmf_ss;
        uint64_t                i_guid;
        scsi_srp_transport_id_t *srptpd;

        ASSERT(tgt != NULL);
        SRPT_DPRINTF_L3("stp_alloc_session, invoked");

        mutex_enter(&tgt->tp_sess_list_lock);

        i_guid = BE_IN64(&i_id[8]);

        stmf_ss = stmf_alloc(STMF_STRUCT_SCSI_SESSION,
            sizeof (srpt_session_t), 0);
        if (stmf_ss == NULL) {
                SRPT_DPRINTF_L2("stp_alloc_session, stmf_alloc"
                    " returned NULL");
                mutex_exit(&tgt->tp_sess_list_lock);
                return (NULL);
        }
        ss = stmf_ss->ss_port_private;
        ASSERT(ss != NULL);

        rw_init(&ss->ss_rwlock, NULL, RW_DRIVER, NULL);
        list_create(&ss->ss_task_list, sizeof (srpt_iu_t),
            offsetof(srpt_iu_t, iu_ss_task_node));

        stmf_ss->ss_rport_id = srpt_stp_alloc_scsi_devid_desc(i_guid);
        /* Setup remote port transport id */
        stmf_ss->ss_rport = stmf_remote_port_alloc(
            sizeof (scsi_srp_transport_id_t));
        stmf_ss->ss_rport->rport_tptid->protocol_id = PROTOCOL_SRP;
        stmf_ss->ss_rport->rport_tptid->format_code = 0;
        srptpd = (scsi_srp_transport_id_t *)stmf_ss->ss_rport->rport_tptid;
        bcopy(i_id, srptpd->srp_name, SRP_PORT_ID_LEN);

        stmf_ss->ss_lport    = tgt->tp_lport;

        ss->ss_ss       = stmf_ss;
        ss->ss_hw_port  = port;
        ss->ss_tgt      = tgt;
        bcopy(i_id, ss->ss_i_id, SRP_PORT_ID_LEN);
        bcopy(t_id, ss->ss_t_id, SRP_PORT_ID_LEN);

        /*
         * Set the alias to include the initiator extension, this will enable
         * the administrator to identify multiple unique sessions originating
         * from the same initiator.
         */
        (void) strlcpy(ss->ss_i_gid, remote_gid, SRPT_ALIAS_LEN);
        (void) strlcpy(ss->ss_t_gid, local_gid, SRPT_ALIAS_LEN);
        EUI_STR(ss->ss_i_name, BE_IN64(&ss->ss_i_id[8]));
        EUI_STR(ss->ss_t_name, BE_IN64(&ss->ss_t_id[0]));
        ALIAS_STR(ss->ss_i_alias, BE_IN64(&ss->ss_i_id[0]),
            BE_IN64(&ss->ss_i_id[8]));
        ALIAS_STR(ss->ss_t_alias, BE_IN64(&ss->ss_t_id[0]),
            BE_IN64(&ss->ss_t_id[8]));
        stmf_ss->ss_rport_alias = ss->ss_i_alias;

        status = stmf_register_scsi_session(tgt->tp_lport, stmf_ss);
        if (status != STMF_SUCCESS) {
                SRPT_DPRINTF_L1("stp_alloc_session, STMF register session"
                    " err(0x%llx)", (u_longlong_t)status);
                list_destroy(&ss->ss_task_list);
                rw_destroy(&ss->ss_rwlock);
                srpt_stp_free_scsi_devid_desc(stmf_ss->ss_rport_id);
                stmf_remote_port_free(stmf_ss->ss_rport);
                stmf_free(stmf_ss);
                mutex_exit(&tgt->tp_sess_list_lock);
                return (NULL);
        }

        list_insert_tail(&tgt->tp_sess_list, ss);
        mutex_exit(&tgt->tp_sess_list_lock);
        return (ss);
}

/*
 * srpt_stp_free_session()
 */
void
srpt_stp_free_session(srpt_session_t *session)
{
        stmf_scsi_session_t     *stmf_ss;
        srpt_target_port_t      *tgt;

        ASSERT(session != NULL);

        tgt = session->ss_tgt;

        ASSERT(tgt != NULL);

        SRPT_DPRINTF_L3("stp_free_session, invoked");

        mutex_enter(&tgt->tp_sess_list_lock);

        stmf_ss = session->ss_ss;

        list_destroy(&session->ss_task_list);
        rw_destroy(&session->ss_rwlock);

        stmf_deregister_scsi_session(tgt->tp_lport, stmf_ss);
        srpt_stp_free_scsi_devid_desc(stmf_ss->ss_rport_id);
        stmf_remote_port_free(stmf_ss->ss_rport);
        list_remove(&tgt->tp_sess_list, session);
        cv_signal(&tgt->tp_sess_complete);
        mutex_exit(&tgt->tp_sess_list_lock);
        stmf_free(stmf_ss);
}

/*
 * srpt_stp_login() - SRP SCSI Target port login
 */
srpt_channel_t *
srpt_stp_login(srpt_target_port_t *tgt, srp_login_req_t *login,
        srp_login_rsp_t *login_rsp, srp_login_rej_t *login_rej,
        uint8_t login_port, char *local_gid, char *remote_gid)
{
        uint32_t        reason;
        uint32_t        req_it_ui_len;
        uint8_t         rsp_flags;
        srpt_ioc_t      *ioc;
        srpt_channel_t  *ch = NULL;
        srpt_channel_t  *next_ch = NULL;
        srpt_session_t  *session = NULL;
        srpt_session_t  sess;

        ASSERT(tgt != NULL);
        ASSERT(login != NULL);
        ASSERT(login_rsp != NULL);
        ASSERT(login_rej != NULL);

        /* Store the string representation of connection info */
        /* for Dtrace probes */
        bzero(&sess, sizeof (srpt_session_t));
        (void) strlcpy(sess.ss_i_gid, remote_gid, SRPT_ALIAS_LEN);
        (void) strlcpy(sess.ss_t_gid, local_gid, SRPT_ALIAS_LEN);
        EUI_STR(sess.ss_i_name,
            BE_IN64(&login->lreq_initiator_port_id[8]));
        EUI_STR(sess.ss_t_name,
            BE_IN64(&login->lreq_target_port_id[0]));
        ALIAS_STR(sess.ss_i_alias,
            BE_IN64(&login->lreq_initiator_port_id[0]),
            BE_IN64(&login->lreq_initiator_port_id[8]));
        ALIAS_STR(sess.ss_t_alias,
            BE_IN64(&login->lreq_target_port_id[0]),
            BE_IN64(&login->lreq_target_port_id[8]));

        DTRACE_SRP_2(login__command, srpt_session_t, &sess,
            srp_login_req_t, login);

        /*
         * The target lock taken here serializes logins to this target
         * and prevents an STMF target port from starting a control
         * operation to transition the target state while a login is
         * being processed.
         */
        bzero(login_rsp, sizeof (srp_login_rsp_t));
        bzero(login_rej, sizeof (srp_login_rej_t));
        mutex_enter(&tgt->tp_lock);
        ioc = tgt->tp_ioc;
        if (ioc == NULL) {
                SRPT_DPRINTF_L1("stp_login, NULL I/O Controller");
                reason = SRP_LOGIN_REJ_UNABLE_TO_ASSOCIATE_I_T_NEXUS;
                goto reject_login;
        }

        /*
         * Validate that the SRP Target ID in the login request specifies
         * this I/O Controller SCSI Target Port.
         */
        if (memcmp(login->lreq_target_port_id, tgt->tp_srp_port_id,
            SRP_PORT_ID_LEN) != 0) {
                SRPT_DPRINTF_L2("stp_login, SRP CM SVC target ID mismatch."
                    " Incoming TgtID 0x%016llx:0x%016llx",
                    (u_longlong_t)BE_IN64(&login->lreq_target_port_id[0]),
                    (u_longlong_t)BE_IN64(&login->lreq_target_port_id[8]));

                reason = SRP_LOGIN_REJ_UNABLE_TO_ASSOCIATE_I_T_NEXUS;
                goto reject_login;
        }

        if (tgt->tp_state != SRPT_TGT_STATE_ONLINE) {
                SRPT_DPRINTF_L2("stp_login, SRP Login target not on-line");
                reason = SRP_LOGIN_REJ_UNABLE_TO_ASSOCIATE_I_T_NEXUS;
                goto reject_login;
        }

        /*
         * Initiator requested IU size must be as large as the specification
         * minimum and no greater than what we chose to support.
         */
        req_it_ui_len = b2h32(login->lreq_req_it_iu_len);
        SRPT_DPRINTF_L2("stp_login, requested iu size = %d", req_it_ui_len);
        if (req_it_ui_len > srpt_iu_size) {
                SRPT_DPRINTF_L2("stp_login, SRP Login IU size (%d) too large",
                    req_it_ui_len);
                reason = SRP_LOGIN_REJ_REQ_IT_IU_LENGTH_TOO_LARGE;
                goto reject_login;
        }
        if (req_it_ui_len < SRP_MIN_IU_SIZE) {
                SRPT_DPRINTF_L2("stp_login, SRP Login IU size (%d) too small",
                    req_it_ui_len);
                reason = SRP_LOGIN_REJ_NO_REASON;
                goto reject_login;
        }

        SRPT_DPRINTF_L2("stp_login, login req InitID 0x%016llx:0x%016llx",
            (u_longlong_t)BE_IN64(&login->lreq_initiator_port_id[0]),
            (u_longlong_t)BE_IN64(&login->lreq_initiator_port_id[8]));
        SRPT_DPRINTF_L2("stp_login, login req TgtID 0x%016llx:0x%016llx",
            (u_longlong_t)BE_IN64(&login->lreq_target_port_id[0]),
            (u_longlong_t)BE_IN64(&login->lreq_target_port_id[8]));

        /*
         * Processing is based on either single channel or multi-channel
         * operation.  In single channel, all current logins for this
         * same I_T_Nexus should be logged out.  In multi-channel
         * mode we would add an additional channel to an existing
         * I_T_Nexus if one currently exists (i.e. reference the
         * same SCSI session).
         */
        rsp_flags = SRP_MULTI_CH_RESULT_NO_EXISTING;

        switch (login->lreq_req_flags & SRP_LOGIN_MULTI_CH_MASK) {

        case SRP_LOGIN_MULTI_CH_SINGLE:
                /*
                 * Only a single channel may be associated with a I_T_Nexus.
                 * Disconnect any channel with the same SRP Initiator and
                 * SRP target IDs.
                 */
                mutex_enter(&tgt->tp_ch_list_lock);
                ch = list_head(&tgt->tp_ch_list);
                while (ch != NULL) {
                        SRPT_DPRINTF_L3("stp_login, compare session,"
                            " ch_state(%d)", ch->ch_state);
                        next_ch = list_next(&tgt->tp_ch_list, ch);

                        if (ch->ch_state != SRPT_CHANNEL_CONNECTING &&
                            ch->ch_state != SRPT_CHANNEL_CONNECTED) {
                                SRPT_DPRINTF_L3("stp_login, compare session,"
                                    " channel not active");
                                ch = next_ch;
                                continue;
                        }

                        ASSERT(ch->ch_session != NULL);
                        SRPT_DPRINTF_L3("stp_login, compare session"
                            " I_ID 0x%016llx:0x%016llx",
                            (u_longlong_t)b2h64(*((uint64_t *)(void *)
                            &ch->ch_session->ss_i_id[0])),
                            (u_longlong_t)b2h64(*((uint64_t *)(void *)
                            &ch->ch_session->ss_i_id[8])));
                        SRPT_DPRINTF_L3("stp_login, compare session"
                            " T_ID 0x%016llx:0x%016llx",
                            (u_longlong_t)b2h64(*((uint64_t *)(void *)
                            &ch->ch_session->ss_t_id[0])),
                            (u_longlong_t)b2h64(*((uint64_t *)(void *)
                            &ch->ch_session->ss_t_id[8])));
                        if ((bcmp(login->lreq_initiator_port_id,
                            ch->ch_session->ss_i_id,
                            SRP_PORT_ID_LEN) == 0) &&
                            (bcmp(login->lreq_target_port_id,
                            ch->ch_session->ss_t_id,
                            SRP_PORT_ID_LEN) == 0)) {
                                /*
                                 * if a session is in the process of connecting,
                                 * reject subsequent equivalent requests.
                                 */
                                if (ch->ch_state == SRPT_CHANNEL_CONNECTING) {
                                        reason = SRP_LOGIN_REJ_INIT_CH_LIMIT;
                                        mutex_exit(&tgt->tp_ch_list_lock);
                                        goto reject_login;
                                }

                                SRPT_DPRINTF_L2("stp_login, terminate"
                                    " existing login");
                                rsp_flags =
                                    SRP_MULTI_CH_RESULT_TERM_EXISTING;
                                srpt_ch_disconnect(ch);
                        }

                        ch = next_ch;
                }
                mutex_exit(&tgt->tp_ch_list_lock);

                /* Create the new session for this SRP login */
                session = srpt_stp_alloc_session(tgt,
                    login->lreq_initiator_port_id,
                    login->lreq_target_port_id, login_port,
                    local_gid, remote_gid);
                if (session == NULL) {
                        SRPT_DPRINTF_L2("stp_login, session allocation"
                            " failed");
                        reason = SRP_LOGIN_REJ_UNABLE_TO_ASSOCIATE_I_T_NEXUS;
                        goto reject_login;
                }
                break;

        case SRP_LOGIN_MULTI_CH_MULTIPLE:
                SRPT_DPRINTF_L2("stp_login, multichannel not supported yet");
                reason = SRP_LOGIN_REJ_MULTI_CH_NOT_SUPPORTED;
                goto reject_login;
                /* break via goto */

        default:
                SRPT_DPRINTF_L2("stp_login, invalid multichannel field (%d)",
                    login->lreq_req_flags & SRP_LOGIN_MULTI_CH_MASK);
                reason = SRP_LOGIN_REJ_NO_REASON;
                goto reject_login;
                /* break via goto */
        }

        /*
         * Create new RDMA channel for this SRP login request.
         * The channel is returned with a single reference which
         * represents the reference held by the CM.
         */
        ch = srpt_ch_alloc(tgt, login_port);
        if (ch == NULL) {
                SRPT_DPRINTF_L2("stp_login, unable to alloc RDMA channel");
                reason = SRP_LOGIN_REJ_INSUFFICIENT_CH_RESOURCES;
                srpt_stp_free_session(session);
                goto reject_login;
        }
        ch->ch_session = session;
        ch->ch_ti_iu_len = b2h32(login->lreq_req_it_iu_len);

        /*
         * Add another reference to the channel which represents
         * a reference placed by the target port and add it to
         * the store of channels logged in for this target port.
         */
        srpt_ch_add_ref(ch);
        mutex_enter(&tgt->tp_ch_list_lock);
        list_insert_tail(&tgt->tp_ch_list, ch);
        mutex_exit(&tgt->tp_ch_list_lock);

        srpt_format_login_rsp(login, login_rsp, rsp_flags);
        mutex_exit(&tgt->tp_lock);
        SRPT_DPRINTF_L2("stp_login, login successful");

        DTRACE_SRP_3(login__response, srpt_session_t, &sess,
            srp_login_rsp_t, login_rsp, srp_login_rej_t, login_rej)

        return (ch);

reject_login:
        srpt_format_login_rej(login, login_rej, reason);
        mutex_exit(&tgt->tp_lock);

        DTRACE_SRP_3(login__response, srpt_session_t, &sess,
            srp_login_rsp_t, login_rsp, srp_login_rej_t, login_rej);

        return (NULL);
}

/*
 * srpt_stp_logout() - SRP logout
 *
 * Logout is not normally initiated in-band, but is so, just
 * initiate a disconnect.
 */
void
srpt_stp_logout(srpt_channel_t *ch)
{
        DTRACE_SRP_1(logout__command, srpt_channel_t, ch);
        SRPT_DPRINTF_L2("stp_logout, invoked for ch (%p)", (void *)ch);
        srpt_ch_disconnect(ch);
}

/*
 * srpt_format_login_rej() - Format login reject IU
 */
static void
srpt_format_login_rej(srp_login_req_t *req, srp_login_rej_t *rej,
        uint32_t reason)
{
        rej->lrej_type   = SRP_IU_LOGIN_REJ;
        rej->lrej_reason = h2b32(reason);
        rej->lrej_tag    = req->lreq_tag;
        rej->lrej_sup_buf_format =
            h2b16(SRP_DIRECT_BUFR_DESC | SRP_INDIRECT_BUFR_DESC);
}

/*
 * srpt_format_login_rsp() - Format login response IU
 */
static void
srpt_format_login_rsp(srp_login_req_t *req, srp_login_rsp_t *rsp,
        uint8_t flags)
{
        rsp->lrsp_type   = SRP_IU_LOGIN_RSP;
        rsp->lrsp_req_limit_delta = h2b32((uint32_t)srpt_send_msg_depth);
        rsp->lrsp_tag    = req->lreq_tag;

        rsp->lrsp_max_it_iu_len = req->lreq_req_it_iu_len;
        /* by def. > min T_IU_LEN */
        rsp->lrsp_max_ti_iu_len = req->lreq_req_it_iu_len;

        rsp->lrsp_sup_buf_format =
            h2b16(SRP_DIRECT_BUFR_DESC | SRP_INDIRECT_BUFR_DESC);
        rsp->lrsp_rsp_flags = flags;
}

/*
 * srpt_stp_add_task()
 */
void
srpt_stp_add_task(srpt_session_t *session, srpt_iu_t *iu)
{
        rw_enter(&session->ss_rwlock, RW_WRITER);
        list_insert_tail(&session->ss_task_list, iu);
        rw_exit(&session->ss_rwlock);
}

/*
 * srpt_stp_remove_task()
 */
void
srpt_stp_remove_task(srpt_session_t *session, srpt_iu_t *iu)
{
        rw_enter(&session->ss_rwlock, RW_WRITER);

        ASSERT(!list_is_empty(&session->ss_task_list));

        list_remove(&session->ss_task_list, iu);
        rw_exit(&session->ss_rwlock);
}