root/usr/src/uts/common/io/comstar/port/pppt/pppt.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.
 * Copyright 2013, Nexenta Systems, Inc. All rights reserved.
 */

#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/nvpair.h>
#include <sys/door.h>
#include <sys/sdt.h>

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

#include "pppt.h"

#define PPPT_VERSION            BUILD_DATE "-1.18dev"
#define PPPT_NAME_VERSION       "COMSTAR PPPT v" PPPT_VERSION

/*
 * DDI entry points.
 */
static int pppt_drv_attach(dev_info_t *, ddi_attach_cmd_t);
static int pppt_drv_detach(dev_info_t *, ddi_detach_cmd_t);
static int pppt_drv_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int pppt_drv_open(dev_t *, int, int, cred_t *);
static int pppt_drv_close(dev_t, int, int, cred_t *);
static boolean_t pppt_drv_busy(void);
static int pppt_drv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);

extern pppt_status_t pppt_ic_so_enable(boolean_t);
extern void pppt_ic_so_disable();
extern void stmf_ic_rx_msg(char *, size_t);

extern struct mod_ops mod_miscops;

static struct cb_ops pppt_cb_ops = {
        pppt_drv_open,  /* cb_open */
        pppt_drv_close, /* cb_close */
        nodev,                  /* cb_strategy */
        nodev,                  /* cb_print */
        nodev,                  /* cb_dump */
        nodev,                  /* cb_read */
        nodev,                  /* cb_write */
        pppt_drv_ioctl,         /* cb_ioctl */
        nodev,                  /* cb_devmap */
        nodev,                  /* cb_mmap */
        nodev,                  /* cb_segmap */
        nochpoll,               /* cb_chpoll */
        ddi_prop_op,            /* cb_prop_op */
        NULL,                   /* cb_streamtab */
        D_MP,                   /* cb_flag */
        CB_REV,                 /* cb_rev */
        nodev,                  /* cb_aread */
        nodev,                  /* cb_awrite */
};

static struct dev_ops pppt_dev_ops = {
        DEVO_REV,               /* devo_rev */
        0,                      /* devo_refcnt */
        pppt_drv_getinfo,       /* devo_getinfo */
        nulldev,                /* devo_identify */
        nulldev,                /* devo_probe */
        pppt_drv_attach,        /* devo_attach */
        pppt_drv_detach,        /* devo_detach */
        nodev,                  /* devo_reset */
        &pppt_cb_ops,           /* devo_cb_ops */
        NULL,                   /* devo_bus_ops */
        NULL,                   /* devo_power */
        ddi_quiesce_not_needed, /* quiesce */
};

static struct modldrv modldrv = {
        &mod_driverops,
        "Proxy Port Provider",
        &pppt_dev_ops,
};

static struct modlinkage modlinkage = {
        MODREV_1,
        &modldrv,
        NULL,
};

pppt_global_t pppt_global;

int pppt_logging = 0;

static int pppt_enable_svc(void);

static void pppt_disable_svc(void);

static int pppt_task_avl_compare(const void *tgt1, const void *tgt2);

static stmf_data_buf_t *pppt_dbuf_alloc(scsi_task_t *task,
    uint32_t size, uint32_t *pminsize, uint32_t flags);

static void pppt_dbuf_free(stmf_dbuf_store_t *ds, stmf_data_buf_t *dbuf);

static void pppt_sess_destroy_task(void *ps_void);

static void pppt_task_sent_status(pppt_task_t *ptask);

static pppt_status_t pppt_task_try_abort(pppt_task_t *ptask);

static void pppt_task_rele(pppt_task_t *ptask);

static void pppt_task_update_state(pppt_task_t *ptask,
    pppt_task_state_t new_state);

/*
 * Lock order:  global --> target --> session --> task
 */

int
_init(void)
{
        int rc;

        mutex_init(&pppt_global.global_lock, NULL, MUTEX_DEFAULT, NULL);
        mutex_init(&pppt_global.global_door_lock, NULL, MUTEX_DEFAULT, NULL);
        pppt_global.global_svc_state = PSS_DETACHED;

        if ((rc = mod_install(&modlinkage)) != 0) {
                mutex_destroy(&pppt_global.global_door_lock);
                mutex_destroy(&pppt_global.global_lock);
                return (rc);
        }

        return (rc);
}

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}

int
_fini(void)
{
        int rc;

        rc = mod_remove(&modlinkage);

        if (rc == 0) {
                mutex_destroy(&pppt_global.global_lock);
                mutex_destroy(&pppt_global.global_door_lock);
        }

        return (rc);
}

/*
 * DDI entry points.
 */

/* ARGSUSED */
static int
pppt_drv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
    void **result)
{
        ulong_t instance = getminor((dev_t)arg);

        switch (cmd) {
        case DDI_INFO_DEVT2DEVINFO:
                *result = pppt_global.global_dip;
                return (DDI_SUCCESS);

        case DDI_INFO_DEVT2INSTANCE:
                *result = (void *)instance;
                return (DDI_SUCCESS);

        default:
                break;
        }

        return (DDI_FAILURE);
}

static int
pppt_drv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        if (cmd != DDI_ATTACH) {
                return (DDI_FAILURE);
        }

        if (ddi_get_instance(dip) != 0) {
                /* we only allow instance 0 to attach */
                return (DDI_FAILURE);
        }

        /* create the minor node */
        if (ddi_create_minor_node(dip, PPPT_MODNAME, S_IFCHR, 0,
            DDI_PSEUDO, 0) != DDI_SUCCESS) {
                cmn_err(CE_WARN, "pppt_drv_attach: "
                    "failed creating minor node");
                return (DDI_FAILURE);
        }

        pppt_global.global_svc_state = PSS_DISABLED;
        pppt_global.global_dip = dip;

        return (DDI_SUCCESS);
}

/*ARGSUSED*/
static int
pppt_drv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        if (cmd != DDI_DETACH)
                return (DDI_FAILURE);

        PPPT_GLOBAL_LOCK();
        if (pppt_drv_busy()) {
                PPPT_GLOBAL_UNLOCK();
                return (EBUSY);
        }

        ddi_remove_minor_node(dip, NULL);
        ddi_prop_remove_all(dip);

        pppt_global.global_svc_state = PSS_DETACHED;

        PPPT_GLOBAL_UNLOCK();

        return (DDI_SUCCESS);
}

/*ARGSUSED*/
static int
pppt_drv_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
        int     rc = 0;

        PPPT_GLOBAL_LOCK();

        switch (pppt_global.global_svc_state) {
        case PSS_DISABLED:
                pppt_global.global_svc_state = PSS_ENABLING;
                PPPT_GLOBAL_UNLOCK();
                rc = pppt_enable_svc();
                PPPT_GLOBAL_LOCK();
                if (rc == 0) {
                        pppt_global.global_svc_state = PSS_ENABLED;
                } else {
                        pppt_global.global_svc_state = PSS_DISABLED;
                }
                break;
        case PSS_DISABLING:
        case PSS_ENABLING:
        case PSS_ENABLED:
                rc = EBUSY;
                break;
        default:
                rc = EFAULT;
                break;
        }

        PPPT_GLOBAL_UNLOCK();

        return (rc);
}

/* ARGSUSED */
static int
pppt_drv_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
        int rc = 0;

        PPPT_GLOBAL_LOCK();

        switch (pppt_global.global_svc_state) {
        case PSS_ENABLED:
                pppt_global.global_svc_state = PSS_DISABLING;
                PPPT_GLOBAL_UNLOCK();
                pppt_disable_svc();
                PPPT_GLOBAL_LOCK();
                pppt_global.global_svc_state = PSS_DISABLED;
                /*
                 * release the door to the daemon
                 */
                mutex_enter(&pppt_global.global_door_lock);
                if (pppt_global.global_door != NULL) {
                        door_ki_rele(pppt_global.global_door);
                        pppt_global.global_door = NULL;
                }
                mutex_exit(&pppt_global.global_door_lock);
                break;
        default:
                rc = EFAULT;
                break;
        }

        PPPT_GLOBAL_UNLOCK();

        return (rc);
}

static boolean_t
pppt_drv_busy(void)
{
        switch (pppt_global.global_svc_state) {
        case PSS_DISABLED:
        case PSS_DETACHED:
                return (B_FALSE);
        default:
                return (B_TRUE);
        }
        /* NOTREACHED */
}

/* ARGSUSED */
static int
pppt_drv_ioctl(dev_t drv, int cmd, intptr_t argp, int flag, cred_t *cred,
    int *retval)
{
        int                             rc;
        void                            *buf;
        size_t                          buf_size;
        pppt_iocdata_t                  iocd;
        door_handle_t                   new_handle;

        if (drv_priv(cred) != 0) {
                return (EPERM);
        }

        rc = ddi_copyin((void *)argp, &iocd, sizeof (iocd), flag);
        if (rc)
                return (EFAULT);

        if (iocd.pppt_version != PPPT_VERSION_1)
                return (EINVAL);

        switch (cmd) {
        case PPPT_MESSAGE:

                /* XXX limit buf_size ? */
                buf_size = (size_t)iocd.pppt_buf_size;
                buf = kmem_alloc(buf_size, KM_SLEEP);
                if (buf == NULL)
                        return (ENOMEM);

                rc = ddi_copyin((void *)(unsigned long)iocd.pppt_buf,
                    buf, buf_size, flag);
                if (rc) {
                        kmem_free(buf, buf_size);
                        return (EFAULT);
                }

                stmf_ic_rx_msg(buf, buf_size);

                kmem_free(buf, buf_size);
                break;
        case PPPT_INSTALL_DOOR:

                new_handle = door_ki_lookup((int)iocd.pppt_door_fd);
                if (new_handle == NULL)
                        return (EINVAL);

                mutex_enter(&pppt_global.global_door_lock);
                ASSERT(pppt_global.global_svc_state == PSS_ENABLED);
                if (pppt_global.global_door != NULL) {
                        /*
                         * There can only be one door installed
                         */
                        mutex_exit(&pppt_global.global_door_lock);
                        door_ki_rele(new_handle);
                        return (EBUSY);
                }
                pppt_global.global_door = new_handle;
                mutex_exit(&pppt_global.global_door_lock);
                break;
        }

        return (rc);
}

/*
 * pppt_enable_svc
 *
 * registers all the configured targets and target portals with STMF
 */
static int
pppt_enable_svc(void)
{
        stmf_port_provider_t    *pp;
        stmf_dbuf_store_t       *dbuf_store;
        int                     rc = 0;

        ASSERT(pppt_global.global_svc_state == PSS_ENABLING);

        /*
         * Make sure that can tell if we have partially allocated
         * in case we need to exit and tear down anything allocated.
         */
        pppt_global.global_dbuf_store = NULL;
        pp = NULL;
        pppt_global.global_pp = NULL;
        pppt_global.global_dispatch_taskq = NULL;
        pppt_global.global_sess_taskq = NULL;

        avl_create(&pppt_global.global_target_list,
            pppt_tgt_avl_compare, sizeof (pppt_tgt_t),
            offsetof(pppt_tgt_t, target_global_ln));

        avl_create(&pppt_global.global_sess_list,
            pppt_sess_avl_compare_by_id, sizeof (pppt_sess_t),
            offsetof(pppt_sess_t, ps_global_ln));

        /*
         * Setup STMF dbuf store.  Tf buffers are associated with a particular
         * lport (FC, SRP) then the dbuf_store should stored in the lport
         * context, otherwise (iSCSI) the dbuf_store should be global.
         */
        dbuf_store = stmf_alloc(STMF_STRUCT_DBUF_STORE, 0, 0);
        if (dbuf_store == NULL) {
                rc = ENOMEM;
                goto tear_down_and_return;
        }
        dbuf_store->ds_alloc_data_buf = pppt_dbuf_alloc;
        dbuf_store->ds_free_data_buf = pppt_dbuf_free;
        dbuf_store->ds_port_private = NULL;
        pppt_global.global_dbuf_store = dbuf_store;

        /* Register port provider */
        pp = stmf_alloc(STMF_STRUCT_PORT_PROVIDER, 0, 0);
        if (pp == NULL) {
                rc = ENOMEM;
                goto tear_down_and_return;
        }

        pp->pp_portif_rev = PORTIF_REV_1;
        pp->pp_instance = 0;
        pp->pp_name = PPPT_MODNAME;
        pp->pp_cb = NULL;

        pppt_global.global_pp = pp;

        if (stmf_register_port_provider(pp) != STMF_SUCCESS) {
                rc = EIO;
                goto tear_down_and_return;
        }

        pppt_global.global_dispatch_taskq = taskq_create("pppt_dispatch",
            1, minclsyspri, 1, INT_MAX, TASKQ_PREPOPULATE);

        pppt_global.global_sess_taskq = taskq_create("pppt_session",
            1, minclsyspri, 1, INT_MAX, TASKQ_PREPOPULATE);

        return (0);

tear_down_and_return:

        if (pppt_global.global_sess_taskq) {
                taskq_destroy(pppt_global.global_sess_taskq);
                pppt_global.global_sess_taskq = NULL;
        }

        if (pppt_global.global_dispatch_taskq) {
                taskq_destroy(pppt_global.global_dispatch_taskq);
                pppt_global.global_dispatch_taskq = NULL;
        }

        if (pppt_global.global_pp)
                pppt_global.global_pp = NULL;

        if (pp)
                stmf_free(pp);

        if (pppt_global.global_dbuf_store) {
                stmf_free(pppt_global.global_dbuf_store);
                pppt_global.global_dbuf_store = NULL;
        }

        avl_destroy(&pppt_global.global_sess_list);
        avl_destroy(&pppt_global.global_target_list);

        return (rc);
}

/*
 * pppt_disable_svc
 *
 * clean up all existing sessions and deregister targets from STMF
 */
static void
pppt_disable_svc(void)
{
        pppt_tgt_t      *tgt, *next_tgt;
        avl_tree_t      delete_target_list;

        ASSERT(pppt_global.global_svc_state == PSS_DISABLING);

        avl_create(&delete_target_list,
            pppt_tgt_avl_compare, sizeof (pppt_tgt_t),
            offsetof(pppt_tgt_t, target_global_ln));

        PPPT_GLOBAL_LOCK();
        for (tgt = avl_first(&pppt_global.global_target_list);
            tgt != NULL;
            tgt = next_tgt) {
                next_tgt = AVL_NEXT(&pppt_global.global_target_list, tgt);
                avl_remove(&pppt_global.global_target_list, tgt);
                avl_add(&delete_target_list, tgt);
                pppt_tgt_async_delete(tgt);
        }
        PPPT_GLOBAL_UNLOCK();

        for (tgt = avl_first(&delete_target_list);
            tgt != NULL;
            tgt = next_tgt) {
                next_tgt = AVL_NEXT(&delete_target_list, tgt);
                mutex_enter(&tgt->target_mutex);
                while ((tgt->target_refcount > 0) ||
                    (tgt->target_state != TS_DELETING)) {
                        cv_wait(&tgt->target_cv, &tgt->target_mutex);
                }
                mutex_exit(&tgt->target_mutex);

                avl_remove(&delete_target_list, tgt);
                pppt_tgt_destroy(tgt);
        }

        taskq_destroy(pppt_global.global_sess_taskq);

        taskq_destroy(pppt_global.global_dispatch_taskq);

        avl_destroy(&pppt_global.global_sess_list);
        avl_destroy(&pppt_global.global_target_list);

        (void) stmf_deregister_port_provider(pppt_global.global_pp);

        stmf_free(pppt_global.global_dbuf_store);
        pppt_global.global_dbuf_store = NULL;

        stmf_free(pppt_global.global_pp);
        pppt_global.global_pp = NULL;
}

/*
 * STMF callbacks
 */

/*ARGSUSED*/
static stmf_data_buf_t *
pppt_dbuf_alloc(scsi_task_t *task, uint32_t size, uint32_t *pminsize,
    uint32_t flags)
{
        stmf_data_buf_t *result;
        pppt_buf_t      *pbuf;
        uint8_t         *buf;

        /* Get buffer */
        buf = kmem_alloc(size, KM_SLEEP);

        /*
         *  Allocate stmf buf with private port provider section
         * (pppt_buf_t)
         */
        result = stmf_alloc(STMF_STRUCT_DATA_BUF, sizeof (pppt_buf_t), 0);
        if (result != NULL) {
                /* Fill in pppt_buf_t */
                pbuf = result->db_port_private;
                pbuf->pbuf_stmf_buf = result;
                pbuf->pbuf_is_immed = B_FALSE;

                /*
                 * Fill in stmf_data_buf_t.  DB_DONT CACHE tells
                 * stmf not to cache buffers but STMF doesn't do
                 * that yet so it's a no-op.  Port providers like
                 * FC and SRP that have buffers associated with the
                 * target port would want to let STMF cache
                 * the buffers.  Port providers like iSCSI would
                 * not want STMF to cache because the buffers are
                 * really associated with a connection, not an
                 * STMF target port so there is no way for STMF
                 * to cache the buffers effectively.  These port
                 * providers should cache buffers internally if
                 * there is significant buffer setup overhead.
                 *
                 * And of course, since STMF doesn't do any internal
                 * caching right now anyway, all port providers should
                 * do what they can to minimize buffer setup overhead.
                 */
                result->db_flags = DB_DONT_CACHE;
                result->db_buf_size = size;
                result->db_data_size = size;
                result->db_sglist_length = 1;
                result->db_sglist[0].seg_addr = buf;
                result->db_sglist[0].seg_length = size;
                return (result);
        } else {
                /*
                 * Couldn't get the stmf_data_buf_t so free the
                 * buffer
                 */
                kmem_free(buf, size);
        }

        return (NULL);
}

/*ARGSUSED*/
static void
pppt_dbuf_free(stmf_dbuf_store_t *ds, stmf_data_buf_t *dbuf)
{
        pppt_buf_t *pbuf = dbuf->db_port_private;

        if (pbuf->pbuf_is_immed) {
                stmf_ic_msg_free(pbuf->pbuf_immed_msg);
        } else {
                kmem_free(dbuf->db_sglist[0].seg_addr,
                    dbuf->db_sglist[0].seg_length);
                stmf_free(dbuf);
        }
}

/*ARGSUSED*/
stmf_status_t
pppt_lport_xfer_data(scsi_task_t *task, stmf_data_buf_t *dbuf,
    uint32_t ioflags)
{
        pppt_task_t             *pppt_task = task->task_port_private;
        pppt_buf_t              *pbuf = dbuf->db_port_private;
        stmf_ic_msg_t           *msg;
        stmf_ic_msg_status_t    ic_msg_status;

        /*
         * If we are aborting then we can ignore this request, otherwise
         * add a reference.
         */
        if (pppt_task_hold(pppt_task) != PPPT_STATUS_SUCCESS) {
                return (STMF_SUCCESS);
        }

        /*
         * If it's not immediate data then start the transfer
         */
        ASSERT(pbuf->pbuf_is_immed == B_FALSE);
        if (dbuf->db_flags & DB_DIRECTION_TO_RPORT) {

                /* Send read data */
                msg = stmf_ic_scsi_data_msg_alloc(
                    pppt_task->pt_task_id,
                    pppt_task->pt_sess->ps_session_id,
                    pppt_task->pt_lun_id,
                    dbuf->db_sglist[0].seg_length,
                    dbuf->db_sglist[0].seg_addr, 0);

                pppt_task->pt_read_buf = pbuf;
                pppt_task->pt_read_xfer_msgid = msg->icm_msgid;

                ic_msg_status = stmf_ic_tx_msg(msg);
                pppt_task_rele(pppt_task);
                if (ic_msg_status != STMF_IC_MSG_SUCCESS) {
                        return (STMF_FAILURE);
                } else {
                        return (STMF_SUCCESS);
                }
        } else if (dbuf->db_flags & DB_DIRECTION_FROM_RPORT) {
                pppt_task_rele(pppt_task);
                return (STMF_FAILURE);
        }

        pppt_task_rele(pppt_task);

        return (STMF_INVALID_ARG);
}

void
pppt_xfer_read_complete(pppt_task_t *pppt_task, stmf_status_t status)
{
        pppt_buf_t              *pppt_buf;
        stmf_data_buf_t         *dbuf;

        /*
         * Caller should have taken a task hold (likely via pppt_task_lookup)
         *
         * Get pppt_buf_t and stmf_data_buf_t pointers
         */
        pppt_buf = pppt_task->pt_read_buf;
        dbuf = pppt_buf->pbuf_stmf_buf;
        dbuf->db_xfer_status = (status == STMF_SUCCESS) ?
            STMF_SUCCESS : STMF_FAILURE;

        /*
         * COMSTAR currently requires port providers to support
         * the DB_SEND_STATUS_GOOD flag even if phase collapse is
         * not supported.  So we will roll our own... pretend we are
         * COMSTAR and ask for a status message.
         */
        if ((dbuf->db_flags & DB_SEND_STATUS_GOOD) &&
            (status == STMF_SUCCESS)) {
                /*
                 * It's possible the task has been aborted since the time we
                 * looked it up.  We need to release the hold before calling
                 * pppt_lport_send_status and as soon as we release the hold
                 * the task may disappear.  Calling pppt_task_done allows us
                 * to determine whether the task has been aborted (in which
                 * case we will stop processing and return) and mark the task
                 * "done" which will prevent the task from being aborted while
                 * we are trying to send the status.
                 */
                if (pppt_task_done(pppt_task) != PPPT_STATUS_SUCCESS) {
                        /* STMF will free task and buffer(s) */
                        pppt_task_rele(pppt_task);
                        return;
                }
                pppt_task_rele(pppt_task);

                if (pppt_lport_send_status(pppt_task->pt_stmf_task, 0)
                    != STMF_SUCCESS) {
                        /* Failed to send status */
                        dbuf->db_xfer_status = STMF_FAILURE;
                        stmf_data_xfer_done(pppt_task->pt_stmf_task, dbuf,
                            STMF_IOF_LPORT_DONE);
                }
        } else {
                pppt_task_rele(pppt_task);
                stmf_data_xfer_done(pppt_task->pt_stmf_task, dbuf, 0);
        }
}

/*ARGSUSED*/
stmf_status_t
pppt_lport_send_status(scsi_task_t *task, uint32_t ioflags)
{
        pppt_task_t *ptask =            task->task_port_private;
        stmf_ic_msg_t                   *msg;
        stmf_ic_msg_status_t            ic_msg_status;

        /*
         * Mark task completed.  If the state indicates it was aborted
         * then we don't need to respond.
         */
        if (pppt_task_done(ptask) == PPPT_STATUS_ABORTED) {
                return (STMF_SUCCESS);
        }

        /*
         * Send status.
         */
        msg = stmf_ic_scsi_status_msg_alloc(
            ptask->pt_task_id,
            ptask->pt_sess->ps_session_id,
            ptask->pt_lun_id,
            0,
            task->task_scsi_status,
            task->task_status_ctrl, task->task_resid,
            task->task_sense_length, task->task_sense_data, 0);

        ic_msg_status = stmf_ic_tx_msg(msg);

        if (ic_msg_status != STMF_IC_MSG_SUCCESS) {
                pppt_task_sent_status(ptask);
                stmf_send_status_done(ptask->pt_stmf_task,
                    STMF_FAILURE, STMF_IOF_LPORT_DONE);
                return (STMF_FAILURE);
        } else {
                pppt_task_sent_status(ptask);
                stmf_send_status_done(ptask->pt_stmf_task,
                    STMF_SUCCESS, STMF_IOF_LPORT_DONE);
                return (STMF_SUCCESS);
        }
}

void
pppt_lport_task_free(scsi_task_t *task)
{
        pppt_task_t *ptask = task->task_port_private;
        pppt_sess_t *ps = ptask->pt_sess;

        pppt_task_rele(ptask);
        pppt_sess_rele(ps);
}

/*ARGSUSED*/
stmf_status_t
pppt_lport_abort(stmf_local_port_t *lport, int abort_cmd, void *arg,
    uint32_t flags)
{
        scsi_task_t     *st = (scsi_task_t *)arg;
        pppt_task_t     *ptask;

        ptask = st->task_port_private;

        if (pppt_task_try_abort(ptask) == PPPT_STATUS_DONE) {
                /*
                 * This task is beyond the point where abort makes sense
                 * and we will soon be sending status.  Tell STMF to
                 * go away.
                 */
                return (STMF_BUSY);
        } else {
                return (STMF_ABORT_SUCCESS);
        }
        /*NOTREACHED*/
}

/*ARGSUSED*/
void
pppt_lport_ctl(stmf_local_port_t *lport, int cmd, void *arg)
{
        switch (cmd) {
        case STMF_CMD_LPORT_ONLINE:
        case STMF_CMD_LPORT_OFFLINE:
        case STMF_ACK_LPORT_ONLINE_COMPLETE:
        case STMF_ACK_LPORT_OFFLINE_COMPLETE:
                pppt_tgt_sm_ctl(lport, cmd, arg);
                break;

        default:
                ASSERT(0);
                break;
        }
}

pppt_sess_t *
pppt_sess_lookup_locked(uint64_t session_id,
    scsi_devid_desc_t *lport_devid, stmf_remote_port_t *rport)
{
        pppt_tgt_t                              *tgt;
        pppt_sess_t                             *ps;
        int                                     lport_cmp;

        ASSERT(mutex_owned(&pppt_global.global_lock));

        /*
         * Look for existing session for this ID
         */
        ps = pppt_sess_lookup_by_id_locked(session_id);
        if (ps == NULL) {
                PPPT_INC_STAT(es_sess_lookup_no_session);
                return (NULL);
        }

        tgt = ps->ps_target;

        mutex_enter(&tgt->target_mutex);

        /* Validate local/remote port names */
        if ((lport_devid->ident_length !=
            tgt->target_stmf_lport->lport_id->ident_length) ||
            (rport->rport_tptid_sz !=
            ps->ps_stmf_sess->ss_rport->rport_tptid_sz)) {
                mutex_exit(&tgt->target_mutex);
                PPPT_INC_STAT(es_sess_lookup_ident_mismatch);
                return (NULL);
        } else {
                lport_cmp = bcmp(lport_devid->ident,
                    tgt->target_stmf_lport->lport_id->ident,
                    lport_devid->ident_length);
                if (lport_cmp != 0 ||
                    (stmf_scsilib_tptid_compare(rport->rport_tptid,
                    ps->ps_stmf_sess->ss_rport->rport_tptid) != B_TRUE)) {
                        mutex_exit(&tgt->target_mutex);
                        PPPT_INC_STAT(es_sess_lookup_ident_mismatch);
                        return (NULL);
                }

                if (tgt->target_state != TS_STMF_ONLINE) {
                        mutex_exit(&tgt->target_mutex);
                        PPPT_INC_STAT(es_sess_lookup_bad_tgt_state);
                        return (NULL);
                }
        }
        mutex_exit(&tgt->target_mutex);

        return (ps);
}

pppt_sess_t *
pppt_sess_lookup_by_id_locked(uint64_t session_id)
{
        pppt_sess_t             tmp_ps;
        pppt_sess_t             *ps;

        ASSERT(mutex_owned(&pppt_global.global_lock));
        tmp_ps.ps_session_id = session_id;
        tmp_ps.ps_closed = 0;
        ps = avl_find(&pppt_global.global_sess_list, &tmp_ps, NULL);
        if (ps != NULL) {
                mutex_enter(&ps->ps_mutex);
                if (!ps->ps_closed) {
                        ps->ps_refcnt++;
                        mutex_exit(&ps->ps_mutex);
                        return (ps);
                }
                mutex_exit(&ps->ps_mutex);
        }

        return (NULL);
}

/* New session */
pppt_sess_t *
pppt_sess_lookup_create(scsi_devid_desc_t *lport_devid,
    scsi_devid_desc_t *rport_devid, stmf_remote_port_t *rport,
    uint64_t session_id, stmf_status_t *statusp)
{
        pppt_tgt_t              *tgt;
        pppt_sess_t             *ps;
        stmf_scsi_session_t     *ss;
        pppt_sess_t             tmp_ps;
        stmf_scsi_session_t     tmp_ss;
        *statusp = STMF_SUCCESS;

        PPPT_GLOBAL_LOCK();

        /*
         * Look for existing session for this ID
         */
        ps = pppt_sess_lookup_locked(session_id, lport_devid, rport);

        if (ps != NULL) {
                PPPT_GLOBAL_UNLOCK();
                return (ps);
        }

        /*
         * No session with that ID, look for another session corresponding
         * to the same IT nexus.
         */
        tgt = pppt_tgt_lookup_locked(lport_devid);
        if (tgt == NULL) {
                *statusp = STMF_NOT_FOUND;
                PPPT_GLOBAL_UNLOCK();
                return (NULL);
        }

        mutex_enter(&tgt->target_mutex);
        if (tgt->target_state != TS_STMF_ONLINE) {
                *statusp = STMF_NOT_FOUND;
                mutex_exit(&tgt->target_mutex);
                PPPT_GLOBAL_UNLOCK();
                /* Can't create session to offline target */
                return (NULL);
        }

        bzero(&tmp_ps, sizeof (tmp_ps));
        bzero(&tmp_ss, sizeof (tmp_ss));
        tmp_ps.ps_stmf_sess = &tmp_ss;
        tmp_ss.ss_rport = rport;

        /*
         * Look for an existing session on this IT nexus
         */
        ps = avl_find(&tgt->target_sess_list, &tmp_ps, NULL);

        if (ps != NULL) {
                /*
                 * Now check the session ID.  It should not match because if
                 * it did we would have found it on the global session list.
                 * If the session ID in the command is higher than the existing
                 * session ID then we need to tear down the existing session.
                 */
                mutex_enter(&ps->ps_mutex);
                ASSERT(ps->ps_session_id != session_id);
                if (ps->ps_session_id > session_id) {
                        /* Invalid session ID */
                        mutex_exit(&ps->ps_mutex);
                        mutex_exit(&tgt->target_mutex);
                        PPPT_GLOBAL_UNLOCK();
                        *statusp = STMF_INVALID_ARG;
                        return (NULL);
                } else {
                        /* Existing session needs to be invalidated */
                        if (!ps->ps_closed) {
                                pppt_sess_close_locked(ps);
                        }
                }
                mutex_exit(&ps->ps_mutex);

                /* Fallthrough and create new session */
        }

        /*
         * Allocate and fill in pppt_session_t with the appropriate data
         * for the protocol.
         */
        ps = kmem_zalloc(sizeof (*ps), KM_SLEEP);

        /* Fill in session fields */
        ps->ps_target = tgt;
        ps->ps_session_id = session_id;

        ss = stmf_alloc(STMF_STRUCT_SCSI_SESSION, 0,
            0);
        if (ss == NULL) {
                mutex_exit(&tgt->target_mutex);
                PPPT_GLOBAL_UNLOCK();
                kmem_free(ps, sizeof (*ps));
                *statusp = STMF_ALLOC_FAILURE;
                return (NULL);
        }

        ss->ss_rport_id = kmem_zalloc(sizeof (scsi_devid_desc_t) +
            rport_devid->ident_length + 1, KM_SLEEP);
        bcopy(rport_devid, ss->ss_rport_id,
            sizeof (scsi_devid_desc_t) + rport_devid->ident_length + 1);

        ss->ss_lport = tgt->target_stmf_lport;

        ss->ss_rport = stmf_remote_port_alloc(rport->rport_tptid_sz);
        bcopy(rport->rport_tptid, ss->ss_rport->rport_tptid,
            rport->rport_tptid_sz);

        if (stmf_register_scsi_session(tgt->target_stmf_lport, ss) !=
            STMF_SUCCESS) {
                mutex_exit(&tgt->target_mutex);
                PPPT_GLOBAL_UNLOCK();
                kmem_free(ss->ss_rport_id,
                    sizeof (scsi_devid_desc_t) + rport_devid->ident_length + 1);
                stmf_remote_port_free(ss->ss_rport);
                stmf_free(ss);
                kmem_free(ps, sizeof (*ps));
                *statusp = STMF_TARGET_FAILURE;
                return (NULL);
        }

        ss->ss_port_private = ps;
        mutex_init(&ps->ps_mutex, NULL, MUTEX_DEFAULT, NULL);
        cv_init(&ps->ps_cv, NULL, CV_DEFAULT, NULL);
        avl_create(&ps->ps_task_list, pppt_task_avl_compare,
            sizeof (pppt_task_t), offsetof(pppt_task_t, pt_sess_ln));
        ps->ps_refcnt = 1;
        ps->ps_stmf_sess = ss;
        avl_add(&tgt->target_sess_list, ps);
        avl_add(&pppt_global.global_sess_list, ps);
        mutex_exit(&tgt->target_mutex);
        PPPT_GLOBAL_UNLOCK();
        stmf_trace("pppt", "New session %p", (void *)ps);

        return (ps);
}

void
pppt_sess_rele(pppt_sess_t *ps)
{
        mutex_enter(&ps->ps_mutex);
        pppt_sess_rele_locked(ps);
        mutex_exit(&ps->ps_mutex);
}

void
pppt_sess_rele_locked(pppt_sess_t *ps)
{
        ASSERT(mutex_owned(&ps->ps_mutex));
        ps->ps_refcnt--;
        if (ps->ps_refcnt == 0) {
                cv_signal(&ps->ps_cv);
        }
}

static void pppt_sess_destroy_task(void *ps_void)
{
        pppt_sess_t *ps = ps_void;
        stmf_scsi_session_t     *ss;

        stmf_trace("pppt", "Session destroy task %p", (void *)ps);

        ss = ps->ps_stmf_sess;
        mutex_enter(&ps->ps_mutex);
        stmf_deregister_scsi_session(ss->ss_lport, ss);
        kmem_free(ss->ss_rport_id,
            sizeof (scsi_devid_desc_t) + ss->ss_rport_id->ident_length + 1);
        stmf_remote_port_free(ss->ss_rport);
        avl_destroy(&ps->ps_task_list);
        mutex_exit(&ps->ps_mutex);
        cv_destroy(&ps->ps_cv);
        mutex_destroy(&ps->ps_mutex);
        stmf_free(ps->ps_stmf_sess);
        kmem_free(ps, sizeof (*ps));

        stmf_trace("pppt", "Session destroy task complete %p", (void *)ps);
}

int
pppt_sess_avl_compare_by_id(const void *void_sess1, const void *void_sess2)
{
        const   pppt_sess_t     *psess1 = void_sess1;
        const   pppt_sess_t     *psess2 = void_sess2;

        if (psess1->ps_session_id < psess2->ps_session_id)
                return (-1);
        else if (psess1->ps_session_id > psess2->ps_session_id)
                return (1);

        /* Allow multiple duplicate sessions if one is closed */
        ASSERT(!(psess1->ps_closed && psess2->ps_closed));
        if (psess1->ps_closed)
                return (-1);
        else if (psess2->ps_closed)
                return (1);

        return (0);
}

int
pppt_sess_avl_compare_by_name(const void *void_sess1, const void *void_sess2)
{
        const   pppt_sess_t     *psess1 = void_sess1;
        const   pppt_sess_t     *psess2 = void_sess2;
        int                     result;

        /* Compare by tptid size */
        if (psess1->ps_stmf_sess->ss_rport->rport_tptid_sz <
            psess2->ps_stmf_sess->ss_rport->rport_tptid_sz) {
                return (-1);
        } else if (psess1->ps_stmf_sess->ss_rport->rport_tptid_sz >
            psess2->ps_stmf_sess->ss_rport->rport_tptid_sz) {
                return (1);
        }

        /* Now compare tptid */
        result = memcmp(psess1->ps_stmf_sess->ss_rport->rport_tptid,
            psess2->ps_stmf_sess->ss_rport->rport_tptid,
            psess1->ps_stmf_sess->ss_rport->rport_tptid_sz);

        if (result < 0) {
                return (-1);
        } else if (result > 0) {
                return (1);
        }

        return (0);
}

void
pppt_sess_close_locked(pppt_sess_t *ps)
{
        pppt_tgt_t      *tgt = ps->ps_target;
        pppt_task_t     *ptask;

        stmf_trace("pppt", "Session close %p", (void *)ps);

        ASSERT(mutex_owned(&pppt_global.global_lock));
        ASSERT(mutex_owned(&tgt->target_mutex));
        ASSERT(mutex_owned(&ps->ps_mutex));
        ASSERT(!ps->ps_closed); /* Caller should ensure session is not closed */

        ps->ps_closed = B_TRUE;
        for (ptask = avl_first(&ps->ps_task_list); ptask != NULL;
            ptask = AVL_NEXT(&ps->ps_task_list, ptask)) {
                mutex_enter(&ptask->pt_mutex);
                if (ptask->pt_state == PTS_ACTIVE) {
                        stmf_abort(STMF_QUEUE_TASK_ABORT, ptask->pt_stmf_task,
                            STMF_ABORTED, NULL);
                }
                mutex_exit(&ptask->pt_mutex);
        }

        /*
         * Now that all the tasks are aborting the session refcnt should
         * go to 0.
         */
        while (ps->ps_refcnt != 0) {
                cv_wait(&ps->ps_cv, &ps->ps_mutex);
        }

        avl_remove(&tgt->target_sess_list, ps);
        avl_remove(&pppt_global.global_sess_list, ps);
        (void) taskq_dispatch(pppt_global.global_sess_taskq,
            &pppt_sess_destroy_task, ps, KM_SLEEP);

        stmf_trace("pppt", "Session close complete %p", (void *)ps);
}

pppt_task_t *
pppt_task_alloc(void)
{
        pppt_task_t     *ptask;
        pppt_buf_t      *immed_pbuf;

        ptask = kmem_alloc(sizeof (pppt_task_t) + sizeof (pppt_buf_t) +
            sizeof (stmf_data_buf_t), KM_NOSLEEP);
        if (ptask != NULL) {
                ptask->pt_state = PTS_INIT;
                ptask->pt_read_buf = NULL;
                ptask->pt_read_xfer_msgid = 0;
                ptask->pt_refcnt = 0;
                mutex_init(&ptask->pt_mutex, NULL, MUTEX_DRIVER, NULL);
                immed_pbuf = (pppt_buf_t *)(ptask + 1);
                bzero(immed_pbuf, sizeof (*immed_pbuf));
                immed_pbuf->pbuf_is_immed = B_TRUE;
                immed_pbuf->pbuf_stmf_buf = (stmf_data_buf_t *)(immed_pbuf + 1);

                bzero(immed_pbuf->pbuf_stmf_buf, sizeof (stmf_data_buf_t));
                immed_pbuf->pbuf_stmf_buf->db_port_private = immed_pbuf;
                immed_pbuf->pbuf_stmf_buf->db_sglist_length = 1;
                immed_pbuf->pbuf_stmf_buf->db_flags = DB_DIRECTION_FROM_RPORT |
                    DB_DONT_CACHE;
                ptask->pt_immed_data = immed_pbuf;
        }

        return (ptask);

}

void
pppt_task_free(pppt_task_t *ptask)
{
        mutex_enter(&ptask->pt_mutex);
        ASSERT(ptask->pt_refcnt == 0);
        mutex_destroy(&ptask->pt_mutex);
        kmem_free(ptask, sizeof (pppt_task_t) + sizeof (pppt_buf_t) +
            sizeof (stmf_data_buf_t));
}

pppt_status_t
pppt_task_start(pppt_task_t *ptask)
{
        avl_index_t             where;

        ASSERT(ptask->pt_state == PTS_INIT);

        mutex_enter(&ptask->pt_sess->ps_mutex);
        mutex_enter(&ptask->pt_mutex);
        if (avl_find(&ptask->pt_sess->ps_task_list, ptask, &where) == NULL) {
                pppt_task_update_state(ptask, PTS_ACTIVE);
                /* Manually increment refcnt, sincd we hold the mutex... */
                ptask->pt_refcnt++;
                avl_insert(&ptask->pt_sess->ps_task_list, ptask, where);
                mutex_exit(&ptask->pt_mutex);
                mutex_exit(&ptask->pt_sess->ps_mutex);
                return (PPPT_STATUS_SUCCESS);
        }
        mutex_exit(&ptask->pt_mutex);
        mutex_exit(&ptask->pt_sess->ps_mutex);

        return (PPPT_STATUS_FAIL);
}

pppt_status_t
pppt_task_done(pppt_task_t *ptask)
{
        pppt_status_t   pppt_status = PPPT_STATUS_SUCCESS;
        boolean_t       remove = B_FALSE;

        mutex_enter(&ptask->pt_mutex);

        switch (ptask->pt_state) {
        case PTS_ACTIVE:
                remove = B_TRUE;
                pppt_task_update_state(ptask, PTS_DONE);
                break;
        case PTS_ABORTED:
                pppt_status = PPPT_STATUS_ABORTED;
                break;
        case PTS_DONE:
                /* Repeat calls are OK.  Do nothing, return success */
                break;
        default:
                ASSERT(0);
        }

        mutex_exit(&ptask->pt_mutex);

        if (remove) {
                mutex_enter(&ptask->pt_sess->ps_mutex);
                avl_remove(&ptask->pt_sess->ps_task_list, ptask);
                mutex_exit(&ptask->pt_sess->ps_mutex);
                /* Out of the AVL tree, so drop a reference. */
                pppt_task_rele(ptask);
        }

        return (pppt_status);
}

void
pppt_task_sent_status(pppt_task_t *ptask)
{
        /*
         * If STMF tries to abort a task after the task state changed to
         * PTS_DONE (meaning all task processing is complete from
         * the port provider perspective) then we return STMF_BUSY
         * from pppt_lport_abort.  STMF will return after a short interval
         * but our calls to stmf_send_status_done will be ignored since
         * STMF is aborting the task.  That's where this state comes in.
         * This state essentially says we are calling stmf_send_status_done
         * so we will not be touching the task again.  The next time
         * STMF calls pppt_lport_abort we will return a success full
         * status and the abort will succeed.
         */
        mutex_enter(&ptask->pt_mutex);
        pppt_task_update_state(ptask, PTS_SENT_STATUS);
        mutex_exit(&ptask->pt_mutex);
}

pppt_task_t *
pppt_task_lookup(stmf_ic_msgid_t msgid)
{
        pppt_tgt_t      *tgt;
        pppt_sess_t     *sess;
        pppt_task_t     lookup_task;
        pppt_task_t     *result;

        bzero(&lookup_task, sizeof (lookup_task));
        lookup_task.pt_task_id = msgid;
        PPPT_GLOBAL_LOCK();
        for (tgt = avl_first(&pppt_global.global_target_list); tgt != NULL;
            tgt = AVL_NEXT(&pppt_global.global_target_list, tgt)) {

                mutex_enter(&tgt->target_mutex);
                for (sess = avl_first(&tgt->target_sess_list); sess != NULL;
                    sess = AVL_NEXT(&tgt->target_sess_list, sess)) {
                        mutex_enter(&sess->ps_mutex);
                        if ((result = avl_find(&sess->ps_task_list,
                            &lookup_task, NULL)) != NULL) {
                                if (pppt_task_hold(result) !=
                                    PPPT_STATUS_SUCCESS) {
                                        result = NULL;
                                }
                                mutex_exit(&sess->ps_mutex);
                                mutex_exit(&tgt->target_mutex);
                                PPPT_GLOBAL_UNLOCK();
                                return (result);
                        }
                        mutex_exit(&sess->ps_mutex);
                }
                mutex_exit(&tgt->target_mutex);
        }
        PPPT_GLOBAL_UNLOCK();

        return (NULL);
}

static int
pppt_task_avl_compare(const void *void_task1, const void *void_task2)
{
        const pppt_task_t       *ptask1 = void_task1;
        const pppt_task_t       *ptask2 = void_task2;

        if (ptask1->pt_task_id < ptask2->pt_task_id)
                return (-1);
        else if (ptask1->pt_task_id > ptask2->pt_task_id)
                return (1);

        return (0);
}

static pppt_status_t
pppt_task_try_abort(pppt_task_t *ptask)
{
        boolean_t       remove = B_FALSE;
        pppt_status_t   pppt_status = PPPT_STATUS_SUCCESS;

        mutex_enter(&ptask->pt_mutex);

        switch (ptask->pt_state) {
        case PTS_ACTIVE:
                remove = B_TRUE;
                pppt_task_update_state(ptask, PTS_ABORTED);
                break;
        case PTS_DONE:
                pppt_status = PPPT_STATUS_DONE;
                break;
        case PTS_SENT_STATUS:
                /*
                 * Already removed so leave remove set to B_FALSE
                 * and leave status set to PPPT_STATUS_SUCCESS.
                 */
                pppt_task_update_state(ptask, PTS_ABORTED);
                break;
        case PTS_ABORTED:
                break;
        default:
                ASSERT(0);
        }

        mutex_exit(&ptask->pt_mutex);

        if (remove) {
                mutex_enter(&ptask->pt_sess->ps_mutex);
                avl_remove(&ptask->pt_sess->ps_task_list, ptask);
                mutex_exit(&ptask->pt_sess->ps_mutex);
                /* Out of the AVL tree, so drop a reference. */
                pppt_task_rele(ptask);
        }

        return (pppt_status);
}

pppt_status_t
pppt_task_hold(pppt_task_t *ptask)
{
        pppt_status_t   pppt_status = PPPT_STATUS_SUCCESS;

        mutex_enter(&ptask->pt_mutex);
        if (ptask->pt_state == PTS_ACTIVE) {
                ptask->pt_refcnt++;
        } else {
                pppt_status = PPPT_STATUS_FAIL;
        }
        mutex_exit(&ptask->pt_mutex);

        return (pppt_status);
}

static void
pppt_task_rele(pppt_task_t *ptask)
{
        boolean_t freeit;

        mutex_enter(&ptask->pt_mutex);
        ptask->pt_refcnt--;
        freeit = (ptask->pt_refcnt == 0);
        mutex_exit(&ptask->pt_mutex);
        if (freeit)
                pppt_task_free(ptask);
}

static void
pppt_task_update_state(pppt_task_t *ptask,
    pppt_task_state_t new_state)
{
        PPPT_LOG(CE_NOTE, "task %p %d -> %d", (void *)ptask,
            ptask->pt_state, new_state);

        ASSERT(mutex_owned(&ptask->pt_mutex));
        ptask->pt_state = new_state;
}