root/usr/src/uts/common/io/scsi/adapters/iscsi/iscsi_conn.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) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 *
 * iSCSI connection interfaces
 */

#define ISCSI_ICS_NAMES
#include "iscsi.h"
#include "persistent.h"
#include <sys/bootprops.h>

extern ib_boot_prop_t   *iscsiboot_prop;

static void iscsi_client_notify_task(void *cn_task_void);

static void iscsi_conn_flush_active_cmds(iscsi_conn_t *icp);

#define SHUTDOWN_TIMEOUT        180 /* seconds */

extern int modrootloaded;

boolean_t iscsi_conn_logging = B_FALSE;

#define ISCSI_LOGIN_TPGT_NEGO_ERROR(icp) \
        (((icp)->conn_login_state == LOGIN_ERROR) && \
        ((icp)->conn_login_status == ISCSI_STATUS_LOGIN_TPGT_NEGO_FAIL))

/*
 * +--------------------------------------------------------------------+
 * | External Connection Interfaces                                     |
 * +--------------------------------------------------------------------+
 */

/*
 * iscsi_conn_create - This creates an iscsi connection structure and
 * associates it with a session structure.  The session's sess_conn_list_rwlock
 * should be held as a writer before calling this function.
 */
iscsi_status_t
iscsi_conn_create(struct sockaddr *addr, iscsi_sess_t *isp, iscsi_conn_t **icpp)
{
        iscsi_conn_t    *icp    = NULL;
        char            th_name[ISCSI_TH_MAX_NAME_LEN];

        /* See if this connection already exists */
        for (icp = isp->sess_conn_list; icp; icp = icp->conn_next) {

                /*
                 * Compare the ioctl information to see if
                 * its a match for this connection.  (This
                 * is done by making sure the IPs are of
                 * the same size and then they are the
                 * same value.
                 */
                if (bcmp(&icp->conn_base_addr, addr,
                    SIZEOF_SOCKADDR(addr)) == 0) {
                        /* It's a match, record this connection */
                        break;
                }
        }

        /* If icp is found return it */
        if (icp != NULL) {
                *icpp = icp;
                return (ISCSI_STATUS_SUCCESS);
        }

        /* We are creating the connection, allocate, and setup */
        icp = (iscsi_conn_t *)kmem_zalloc(sizeof (iscsi_conn_t), KM_SLEEP);

        /*
         * Setup connection
         */
        icp->conn_sig                   = ISCSI_SIG_CONN;
        icp->conn_state                 = ISCSI_CONN_STATE_FREE;
        mutex_init(&icp->conn_state_mutex, NULL, MUTEX_DRIVER, NULL);
        cv_init(&icp->conn_state_change, NULL, CV_DRIVER, NULL);
        mutex_init(&icp->conn_login_mutex, NULL, MUTEX_DRIVER, NULL);
        cv_init(&icp->conn_login_cv, NULL, CV_DRIVER, NULL);
        icp->conn_state_destroy         = B_FALSE;
        idm_sm_audit_init(&icp->conn_state_audit);
        icp->conn_sess                  = isp;

        mutex_enter(&iscsi_oid_mutex);
        icp->conn_oid = iscsi_oid++;
        mutex_exit(&iscsi_oid_mutex);

        /*
         * IDM CN taskq
         */

        if (snprintf(th_name, sizeof (th_name) - 1,
            ISCSI_CONN_CN_TASKQ_NAME_FORMAT,
            icp->conn_sess->sess_hba->hba_oid, icp->conn_sess->sess_oid,
            icp->conn_oid) >= sizeof (th_name)) {
                cv_destroy(&icp->conn_state_change);
                mutex_destroy(&icp->conn_state_mutex);
                kmem_free(icp, sizeof (iscsi_conn_t));
                *icpp = NULL;
                return (ISCSI_STATUS_INTERNAL_ERROR);
        }

        icp->conn_cn_taskq =
            ddi_taskq_create(icp->conn_sess->sess_hba->hba_dip, th_name, 1,
            TASKQ_DEFAULTPRI, 0);
        if (icp->conn_cn_taskq == NULL) {
                cv_destroy(&icp->conn_state_change);
                mutex_destroy(&icp->conn_state_mutex);
                kmem_free(icp, sizeof (iscsi_conn_t));
                *icpp = NULL;
                return (ISCSI_STATUS_INTERNAL_ERROR);
        }

        /* Creation of the transfer thread */
        if (snprintf(th_name, sizeof (th_name) - 1, ISCSI_CONN_TXTH_NAME_FORMAT,
            icp->conn_sess->sess_hba->hba_oid, icp->conn_sess->sess_oid,
            icp->conn_oid) >= sizeof (th_name)) {
                cv_destroy(&icp->conn_state_change);
                mutex_destroy(&icp->conn_state_mutex);
                kmem_free(icp, sizeof (iscsi_conn_t));
                ddi_taskq_destroy(icp->conn_cn_taskq);
                *icpp = NULL;
                return (ISCSI_STATUS_INTERNAL_ERROR);
        }

        icp->conn_tx_thread = iscsi_thread_create(isp->sess_hba->hba_dip,
            th_name, iscsi_tx_thread, icp);

        /* setup connection queues */
        iscsi_init_queue(&icp->conn_queue_active);
        iscsi_init_queue(&icp->conn_queue_idm_aborting);

        bcopy(addr, &icp->conn_base_addr, sizeof (icp->conn_base_addr));

        /* Add new connection to the session connection list */
        icp->conn_cid = isp->sess_conn_next_cid++;
        if (isp->sess_conn_list == NULL) {
                isp->sess_conn_list = isp->sess_conn_list_last_ptr = icp;
        } else {
                isp->sess_conn_list_last_ptr->conn_next = icp;
                isp->sess_conn_list_last_ptr = icp;
        }

        KSTAT_INC_SESS_CNTR_CONN(isp);
        (void) iscsi_conn_kstat_init(icp);

        *icpp = icp;

        return (ISCSI_STATUS_SUCCESS);
}

/*
 * iscsi_conn_online - This attempts to take a connection from
 * ISCSI_CONN_STATE_FREE to ISCSI_CONN_STATE_LOGGED_IN.
 */
iscsi_status_t
iscsi_conn_online(iscsi_conn_t *icp)
{
        iscsi_task_t    *itp;
        iscsi_status_t  rval;

        ASSERT(icp != NULL);
        ASSERT(mutex_owned(&icp->conn_state_mutex));
        ASSERT(icp->conn_state == ISCSI_CONN_STATE_FREE);

        /*
         * If we are attempting to connect then for the purposes of the
         * other initiator code we are effectively in ISCSI_CONN_STATE_IN_LOGIN.
         */
        iscsi_conn_update_state_locked(icp, ISCSI_CONN_STATE_IN_LOGIN);
        mutex_exit(&icp->conn_state_mutex);

        /*
         * Sync base connection information before login
         * A login redirection might have shifted the
         * current information from the base.
         */
        bcopy(&icp->conn_base_addr, &icp->conn_curr_addr,
            sizeof (icp->conn_curr_addr));

        itp = kmem_zalloc(sizeof (iscsi_task_t), KM_SLEEP);
        ASSERT(itp != NULL);

        itp->t_arg = icp;
        itp->t_blocking = B_TRUE;
        rval = iscsi_login_start(itp);
        kmem_free(itp, sizeof (iscsi_task_t));

        mutex_enter(&icp->conn_state_mutex);

        return (rval);
}

/*
 * iscsi_conn_offline - This attempts to take a connection from
 * any state to ISCSI_CONN_STATE_FREE.
 */
iscsi_status_t
iscsi_conn_offline(iscsi_conn_t *icp)
{
        clock_t         delay;

        ASSERT(icp != NULL);

        /*
         * We can only destroy a connection if its either in
         * a state of FREE or LOGGED.  The other states are
         * transitionary and its unsafe to perform actions
         * on the connection in those states.  Set a flag
         * on the connection to influence the transitions
         * to quickly complete.  Then wait for a state
         * transition.
         *
         * ISCSI_CONN_STATE_LOGGED_IN is set immediately at the
         * start of CN_NOTIFY_FFP processing. icp->conn_state_ffp
         * is set to true at the end of ffp processing, at which
         * point any session updates are complete.  We don't
         * want to start offlining the connection before we're
         * done completing the FFP processing since this might
         * interrupt the discovery process.
         */
        delay = ddi_get_lbolt() + SEC_TO_TICK(SHUTDOWN_TIMEOUT);
        mutex_enter(&icp->conn_state_mutex);
        icp->conn_state_destroy = B_TRUE;
        while ((((icp->conn_state != ISCSI_CONN_STATE_FREE) &&
            (icp->conn_state != ISCSI_CONN_STATE_LOGGED_IN)) ||
            ((icp->conn_state == ISCSI_CONN_STATE_LOGGED_IN) &&
            !icp->conn_state_ffp)) &&
            (ddi_get_lbolt() < delay)) {
                /* wait for transition */
                (void) cv_timedwait(&icp->conn_state_change,
                    &icp->conn_state_mutex, delay);
        }

        switch (icp->conn_state) {
        case ISCSI_CONN_STATE_FREE:
                break;
        case ISCSI_CONN_STATE_LOGGED_IN:
                if (icp->conn_state_ffp) {
                        /* Hold is released in iscsi_handle_logout */
                        idm_conn_hold(icp->conn_ic);
                        (void) iscsi_handle_logout(icp);
                } else {
                        icp->conn_state_destroy = B_FALSE;
                        mutex_exit(&icp->conn_state_mutex);
                        return (ISCSI_STATUS_INTERNAL_ERROR);
                }
                break;
        case ISCSI_CONN_STATE_IN_LOGIN:
        case ISCSI_CONN_STATE_IN_LOGOUT:
        case ISCSI_CONN_STATE_FAILED:
        case ISCSI_CONN_STATE_POLLING:
        default:
                icp->conn_state_destroy = B_FALSE;
                mutex_exit(&icp->conn_state_mutex);
                return (ISCSI_STATUS_INTERNAL_ERROR);
        }
        mutex_exit(&icp->conn_state_mutex);

        return (ISCSI_STATUS_SUCCESS);
}

/*
 * iscsi_conn_destroy - This destroys an iscsi connection structure
 * and de-associates it with the session.  The connection should
 * already been in the ISCSI_CONN_STATE_FREE when attempting this
 * operation.
 */
iscsi_status_t
iscsi_conn_destroy(iscsi_conn_t *icp)
{
        iscsi_sess_t    *isp;
        iscsi_conn_t    *t_icp;

        ASSERT(icp != NULL);
        isp = icp->conn_sess;
        ASSERT(isp != NULL);

        if (icp->conn_state != ISCSI_CONN_STATE_FREE) {
                return (ISCSI_STATUS_INTERNAL_ERROR);
        }

        /* Destroy transfer thread */
        iscsi_thread_destroy(icp->conn_tx_thread);
        ddi_taskq_destroy(icp->conn_cn_taskq);

        /* Terminate connection queues */
        iscsi_destroy_queue(&icp->conn_queue_idm_aborting);
        iscsi_destroy_queue(&icp->conn_queue_active);

        cv_destroy(&icp->conn_login_cv);
        mutex_destroy(&icp->conn_login_mutex);
        cv_destroy(&icp->conn_state_change);
        mutex_destroy(&icp->conn_state_mutex);

        /*
         * Remove connection from sessions linked list.
         */
        if (isp->sess_conn_list == icp) {
                /* connection first item in list */
                isp->sess_conn_list = icp->conn_next;
                /*
                 * check if this is also the last item in the list
                 */
                if (isp->sess_conn_list_last_ptr == icp) {
                        isp->sess_conn_list_last_ptr = NULL;
                }
        } else {
                /*
                 * search session list for icp pointing
                 * to connection being removed.  Then
                 * update that connections next pointer.
                 */
                t_icp = isp->sess_conn_list;
                while (t_icp->conn_next != NULL) {
                        if (t_icp->conn_next == icp) {
                                break;
                        }
                        t_icp = t_icp->conn_next;
                }
                if (t_icp->conn_next == icp) {
                        t_icp->conn_next = icp->conn_next;
                        /*
                         * if this is the last connection in the list
                         * update the last_ptr to point to t_icp
                         */
                        if (isp->sess_conn_list_last_ptr == icp) {
                                isp->sess_conn_list_last_ptr = t_icp;
                        }
                } else {
                        /* couldn't find session */
                        ASSERT(FALSE);
                }
        }

        /* Free this Connections Data */
        iscsi_conn_kstat_term(icp);
        kmem_free(icp, sizeof (iscsi_conn_t));

        return (ISCSI_STATUS_SUCCESS);
}


/*
 * iscsi_conn_set_login_min_max - set min/max login window
 *
 * Used to set the min and max login window.  Input values
 * are in seconds.
 */
void
iscsi_conn_set_login_min_max(iscsi_conn_t *icp, int min, int max)
{
        ASSERT(icp != NULL);

        icp->conn_login_min = ddi_get_lbolt() + SEC_TO_TICK(min);
        icp->conn_login_max = ddi_get_lbolt() + SEC_TO_TICK(max);
}


/*
 * Process the idm notifications
 */
idm_status_t
iscsi_client_notify(idm_conn_t *ic, idm_client_notify_t icn, uintptr_t data)
{
        iscsi_cn_task_t         *cn;
        iscsi_conn_t            *icp = ic->ic_handle;
        iscsi_sess_t            *isp;
        uint32_t                event_count;

        /*
         * Don't access icp if the notification is CN_CONNECT_DESTROY
         * since icp may have already been freed.
         *
         * In particular, we cannot audit the CN_CONNECT_DESTROY event.
         *
         * Handle a few cases immediately, the rest in a task queue.
         */
        switch (icn) {
        case CN_CONNECT_FAIL:
        case CN_LOGIN_FAIL:
                /*
                 * Wakeup any thread waiting for login stuff to happen.
                 */
                ASSERT(icp != NULL);

                mutex_enter(&icp->conn_state_mutex);
                idm_sm_audit_event(&icp->conn_state_audit,
                    SAS_ISCSI_CONN, icp->conn_state, icn, data);
                mutex_exit(&icp->conn_state_mutex);
                iscsi_login_update_state(icp, LOGIN_ERROR);
                return (IDM_STATUS_SUCCESS);

        case CN_READY_FOR_LOGIN:
                idm_conn_hold(ic); /* Released in CN_CONNECT_LOST */
                ASSERT(icp != NULL);

                mutex_enter(&icp->conn_state_mutex);
                idm_sm_audit_event(&icp->conn_state_audit,
                    SAS_ISCSI_CONN, icp->conn_state, icn, data);
                icp->conn_state_idm_connected = B_TRUE;
                cv_broadcast(&icp->conn_state_change);
                mutex_exit(&icp->conn_state_mutex);

                iscsi_login_update_state(icp, LOGIN_READY);
                return (IDM_STATUS_SUCCESS);

        case CN_CONNECT_DESTROY:
                /*
                 * We released any dependecies we had on this object in
                 * either CN_LOGIN_FAIL or CN_CONNECT_LOST so we just need
                 * to destroy the IDM connection now.
                 */
                idm_ini_conn_destroy(ic);
                return (IDM_STATUS_SUCCESS);
        }

        ASSERT(icp != NULL);
        mutex_enter(&icp->conn_state_mutex);
        idm_sm_audit_event(&icp->conn_state_audit,
            SAS_ISCSI_CONN, icp->conn_state, icn, data);
        mutex_exit(&icp->conn_state_mutex);
        isp = icp->conn_sess;

        /*
         * Dispatch notifications to the taskq since they often require
         * long blocking operations.  In the case of CN_CONNECT_DESTROY
         * we actually just want to destroy the connection which we
         * can't do in the IDM taskq context.
         */
        cn = kmem_alloc(sizeof (*cn), KM_SLEEP);

        cn->ct_ic = ic;
        cn->ct_icn = icn;
        cn->ct_data = data;

        idm_conn_hold(ic);

        if (ddi_taskq_dispatch(icp->conn_cn_taskq,
            iscsi_client_notify_task, cn, DDI_SLEEP) != DDI_SUCCESS) {
                idm_conn_rele(ic);
                cmn_err(CE_WARN, "iscsi connection(%u) failure - "
                    "unable to schedule notify task", icp->conn_oid);
                iscsi_conn_update_state(icp, ISCSI_CONN_STATE_FREE);
                event_count = atomic_inc_32_nv(&isp->sess_state_event_count);
                iscsi_sess_enter_state_zone(isp);

                iscsi_sess_state_machine(isp,
                    ISCSI_SESS_EVENT_N6, event_count);

                iscsi_sess_exit_state_zone(isp);
        }

        return (IDM_STATUS_SUCCESS);
}

static void
iscsi_client_notify_task(void *cn_task_void)
{
        iscsi_cn_task_t         *cn_task = cn_task_void;
        iscsi_conn_t            *icp;
        iscsi_sess_t            *isp;
        idm_conn_t              *ic;
        idm_client_notify_t     icn;
        uintptr_t               data;
        idm_ffp_disable_t       disable_type;
        boolean_t               in_login;
        uint32_t                event_count;

        ic = cn_task->ct_ic;
        icn = cn_task->ct_icn;
        data = cn_task->ct_data;

        icp = ic->ic_handle;
        ASSERT(icp != NULL);
        isp = icp->conn_sess;

        switch (icn) {
        case CN_FFP_ENABLED:
                mutex_enter(&icp->conn_state_mutex);
                icp->conn_async_logout = B_FALSE;
                icp->conn_state_ffp = B_TRUE;
                cv_broadcast(&icp->conn_state_change);
                mutex_exit(&icp->conn_state_mutex);

                /*
                 * This logic assumes that the IDM login-snooping code
                 * and the initiator login code will agree to go when
                 * the connection is in FFP or final error received.
                 * The reason we do this is that we don't want to process
                 * CN_FFP_DISABLED until CN_FFP_ENABLED has been full handled.
                 */
                mutex_enter(&icp->conn_login_mutex);
                while ((icp->conn_login_state != LOGIN_FFP) &&
                    (icp->conn_login_state != LOGIN_ERROR)) {
                        cv_wait(&icp->conn_login_cv, &icp->conn_login_mutex);
                }
                mutex_exit(&icp->conn_login_mutex);
                break;
        case CN_FFP_DISABLED:
                disable_type = (idm_ffp_disable_t)data;

                mutex_enter(&icp->conn_state_mutex);
                switch (disable_type) {
                case FD_SESS_LOGOUT:
                case FD_CONN_LOGOUT:
                        if (icp->conn_async_logout) {
                                /*
                                 * Our logout was in response to an
                                 * async logout request so treat this
                                 * like a connection failure (we will
                                 * try to re-establish the connection)
                                 */
                                iscsi_conn_update_state_locked(icp,
                                    ISCSI_CONN_STATE_FAILED);
                        } else {
                                /*
                                 * Logout due to to user config change,
                                 * we will not try to re-establish
                                 * the connection.
                                 */
                                iscsi_conn_update_state_locked(icp,
                                    ISCSI_CONN_STATE_IN_LOGOUT);
                                /*
                                 * Hold off generating the ISCSI_SESS_EVENT_N3
                                 * event until we get the CN_CONNECT_LOST
                                 * notification.  This matches the pre-IDM
                                 * implementation better.
                                 */
                        }
                        break;

                case FD_CONN_FAIL:
                default:
                        if (icp->conn_state == ISCSI_CONN_STATE_IN_LOGIN) {
                                iscsi_conn_update_state_locked(icp,
                                    ISCSI_CONN_STATE_FREE);
                        } else {
                                iscsi_conn_update_state_locked(icp,
                                    ISCSI_CONN_STATE_FAILED);
                        }
                        break;
                }

                icp->conn_state_ffp = B_FALSE;
                cv_broadcast(&icp->conn_state_change);
                mutex_exit(&icp->conn_state_mutex);

                break;
        case CN_CONNECT_LOST:
                /*
                 * We only care about CN_CONNECT_LOST if we've logged in.  IDM
                 * sends a flag as the data payload to indicate whether we
                 * were trying to login.  The CN_LOGIN_FAIL notification
                 * gives us what we need to know for login failures and
                 * otherwise we will need to keep a bunch of state to know
                 * what CN_CONNECT_LOST means to us.
                 */
                in_login = (boolean_t)data;
                if (in_login ||
                    (icp->conn_prev_state == ISCSI_CONN_STATE_IN_LOGIN)) {
                        mutex_enter(&icp->conn_state_mutex);

                        icp->conn_state_idm_connected = B_FALSE;
                        cv_broadcast(&icp->conn_state_change);
                        mutex_exit(&icp->conn_state_mutex);

                        /* Release connect hold from CN_READY_FOR_LOGIN */
                        idm_conn_rele(ic);
                        break;
                }

                /* Any remaining commands are never going to finish */
                iscsi_conn_flush_active_cmds(icp);

                /*
                 * The connection is no longer active so cleanup any
                 * references to the connection and release any holds so
                 * that IDM can finish cleanup.
                 */
                mutex_enter(&icp->conn_state_mutex);
                if (icp->conn_state != ISCSI_CONN_STATE_FAILED) {
                        mutex_exit(&icp->conn_state_mutex);
                        event_count = atomic_inc_32_nv(
                            &isp->sess_state_event_count);
                        iscsi_sess_enter_state_zone(isp);

                        iscsi_sess_state_machine(isp, ISCSI_SESS_EVENT_N3,
                            event_count);

                        iscsi_sess_exit_state_zone(isp);

                        mutex_enter(&icp->conn_state_mutex);
                        iscsi_conn_update_state_locked(icp,
                            ISCSI_CONN_STATE_FREE);
                } else {
                        mutex_exit(&icp->conn_state_mutex);
                        event_count = atomic_inc_32_nv(
                            &isp->sess_state_event_count);
                        iscsi_sess_enter_state_zone(isp);

                        iscsi_sess_state_machine(isp,
                            ISCSI_SESS_EVENT_N5, event_count);

                        iscsi_sess_exit_state_zone(isp);

                        /*
                         * If session type is NORMAL, try to reestablish the
                         * connection.
                         */
                        if ((isp->sess_type == ISCSI_SESS_TYPE_NORMAL) &&
                            !(ISCSI_LOGIN_TPGT_NEGO_ERROR(icp))) {
                                iscsi_conn_retry(isp, icp);
                                mutex_enter(&icp->conn_state_mutex);
                        } else {
                                event_count = atomic_inc_32_nv(
                                    &isp->sess_state_event_count);
                                iscsi_sess_enter_state_zone(isp);

                                iscsi_sess_state_machine(isp,
                                    ISCSI_SESS_EVENT_N6, event_count);

                                iscsi_sess_exit_state_zone(isp);

                                mutex_enter(&icp->conn_state_mutex);
                                iscsi_conn_update_state_locked(icp,
                                    ISCSI_CONN_STATE_FREE);
                        }
                }

                (void) iscsi_thread_stop(icp->conn_tx_thread);

                icp->conn_state_idm_connected = B_FALSE;
                cv_broadcast(&icp->conn_state_change);
                mutex_exit(&icp->conn_state_mutex);

                /* Release connect hold from CN_READY_FOR_LOGIN */
                idm_conn_rele(ic);
                break;
        default:
                ISCSI_CONN_LOG(CE_WARN,
                    "iscsi_client_notify: unknown notification: "
                    "%x: NOT IMPLEMENTED YET: icp: %p ic: %p ",
                    icn, (void *)icp, (void *)ic);
                break;
        }
        /* free the task notify structure we allocated in iscsi_client_notify */
        kmem_free(cn_task, sizeof (*cn_task));

        /* Release the hold we acquired in iscsi_client_notify */
        idm_conn_rele(ic);
}

/*
 * iscsi_conn_sync_params - used to update connection parameters
 *
 * Used to update connection parameters with current configured
 * parameters in the persistent store.  This should be called
 * before starting to make a new iscsi connection in iscsi_login.
 */
iscsi_status_t
iscsi_conn_sync_params(iscsi_conn_t *icp)
{
        iscsi_sess_t            *isp;
        iscsi_hba_t             *ihp;
        int                     param_id;
        persistent_param_t      pp;
        persistent_tunable_param_t      ptp;
        iscsi_config_sess_t     *ics;
        int                     idx, size;
        char                    *name;

        ASSERT(icp != NULL);
        ASSERT((icp->conn_state == ISCSI_CONN_STATE_IN_LOGIN) ||
            (icp->conn_state == ISCSI_CONN_STATE_FAILED) ||
            (icp->conn_state == ISCSI_CONN_STATE_POLLING));
        isp = icp->conn_sess;
        ASSERT(isp != NULL);
        ihp = isp->sess_hba;
        ASSERT(ihp != NULL);

        /*
         * Check if someone is trying to destroy this
         * connection.  If so fail the sync request,
         * as a method of fast fail.
         */
        if (icp->conn_state_destroy == B_TRUE) {
                return (ISCSI_STATUS_SHUTDOWN);
        }

        bzero(&pp, sizeof (pp));

        /* First get a copy of the HBA params */
        bcopy(&ihp->hba_params, &icp->conn_params,
            sizeof (iscsi_login_params_t));
        bcopy(&ihp->hba_tunable_params, &icp->conn_tunable_params,
            sizeof (iscsi_tunable_params_t));

        /*
         * Now we need to get the session configured
         * values from the persistent store and apply
         * them to our connection.
         */
        (void) persistent_param_get((char *)isp->sess_name, &pp);
        for (param_id = 0; param_id < ISCSI_NUM_LOGIN_PARAM;
            param_id++) {
                if (iscsiboot_prop && modrootloaded &&
                    !iscsi_chk_bootlun_mpxio(ihp) && isp->sess_boot) {
                        /*
                         * iscsi boot with mpxio disabled
                         * while iscsi booting target's parameter overriden
                         * do no update target's parameters.
                         */
                        if (pp.p_bitmap) {
                                cmn_err(CE_NOTE, "Adopting "
                                    " default login parameters in"
                                    " boot session as MPxIO is disabled");
                        }
                        break;
                }
                if (pp.p_bitmap & (1 << param_id)) {

                        switch (param_id) {
                        /*
                         * Boolean parameters
                         */
                        case ISCSI_LOGIN_PARAM_DATA_SEQUENCE_IN_ORDER:
                                icp->conn_params.data_pdu_in_order =
                                    pp.p_params.data_pdu_in_order;
                                break;
                        case ISCSI_LOGIN_PARAM_IMMEDIATE_DATA:
                                icp->conn_params.immediate_data =
                                    pp.p_params.immediate_data;
                                break;
                        case ISCSI_LOGIN_PARAM_INITIAL_R2T:
                                icp->conn_params.initial_r2t =
                                    pp.p_params.initial_r2t;
                                break;
                        case ISCSI_LOGIN_PARAM_DATA_PDU_IN_ORDER:
                                icp->conn_params.data_pdu_in_order =
                                    pp.p_params.data_pdu_in_order;
                                break;
                        /*
                         * Integer parameters
                         */
                        case ISCSI_LOGIN_PARAM_HEADER_DIGEST:
                                icp->conn_params.header_digest =
                                    pp.p_params.header_digest;
                                break;
                        case ISCSI_LOGIN_PARAM_DATA_DIGEST:
                                icp->conn_params.data_digest =
                                    pp.p_params.data_digest;
                                break;
                        case ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_RETAIN:
                                icp->conn_params.default_time_to_retain =
                                    pp.p_params.default_time_to_retain;
                                break;
                        case ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_WAIT:
                                icp->conn_params.default_time_to_wait =
                                    pp.p_params.default_time_to_wait;
                                break;
                        case ISCSI_LOGIN_PARAM_MAX_RECV_DATA_SEGMENT_LENGTH:
                                icp->conn_params.max_recv_data_seg_len =
                                    pp.p_params.max_recv_data_seg_len;
                                break;
                        case ISCSI_LOGIN_PARAM_FIRST_BURST_LENGTH:
                                icp->conn_params.first_burst_length =
                                    pp.p_params.first_burst_length;
                                break;
                        case ISCSI_LOGIN_PARAM_MAX_BURST_LENGTH:
                                icp->conn_params.max_burst_length =
                                    pp.p_params.max_burst_length;
                                break;

                        /*
                         * Integer parameters which currently are unsettable
                         */
                        case ISCSI_LOGIN_PARAM_MAX_CONNECTIONS:
                                /* FALLTHRU */
                        case ISCSI_LOGIN_PARAM_OUTSTANDING_R2T:
                                /* FALLTHRU */
                        case ISCSI_LOGIN_PARAM_ERROR_RECOVERY_LEVEL:
                                /* FALLTHRU */
                        default:
                                break;
                        }
                }
        }

        if (persistent_get_tunable_param((char *)isp->sess_name, &ptp) ==
            B_TRUE) {
                if (ptp.p_bitmap & ISCSI_TUNABLE_PARAM_RX_TIMEOUT_VALUE) {
                        icp->conn_tunable_params.recv_login_rsp_timeout =
                            ptp.p_params.recv_login_rsp_timeout;
                }
                if (ptp.p_bitmap & ISCSI_TUNABLE_PARAM_CONN_LOGIN_MAX) {
                        icp->conn_tunable_params.conn_login_max =
                            ptp.p_params.conn_login_max;
                }
                if (ptp.p_bitmap & ISCSI_TUNABLE_PARAM_LOGIN_POLLING_DELAY) {
                        icp->conn_tunable_params.polling_login_delay =
                            ptp.p_params.polling_login_delay;
                }
        }

        /* Skip binding checks on discovery sessions */
        if (isp->sess_type == ISCSI_SESS_TYPE_DISCOVERY) {
                return (ISCSI_STATUS_SUCCESS);
        }

        /*
         * Now we need to get the current optional connection
         * binding information.
         */
        /* setup initial buffer for configured session information */
        size = sizeof (*ics);
        ics = kmem_zalloc(size, KM_SLEEP);
        ics->ics_in = 1;

        /* get configured sessions information */
        name = (char *)isp->sess_name;
        if (persistent_get_config_session(name, ics) == B_FALSE) {
                /*
                 * If we were unable to get target level information
                 * then check the initiator level information.
                 */
                name = (char *)isp->sess_hba->hba_name;
                if (persistent_get_config_session(name, ics) == B_FALSE) {
                        /*
                         * No hba information is found.  So assume default
                         * one session unbound behavior.
                         */
                        ics->ics_out = 1;
                        ics->ics_bound = B_FALSE;
                }
        }

        if (iscsiboot_prop && (ics->ics_out > 1) && isp->sess_boot &&
            !iscsi_chk_bootlun_mpxio(ihp)) {
                /*
                 * iscsi booting session with mpxio disabled,
                 * no need set multiple sessions for booting session
                 */
                ics->ics_out = 1;
                ics->ics_bound = B_FALSE;
                cmn_err(CE_NOTE, "MPxIO is disabled,"
                    " no need to configure multiple boot sessions");
        }

        /*
         * Check to make sure this session is still a configured
         * session.  The user might have decreased the session
         * count. (NOTE: byte 5 of the sess_isid is the session
         * count (via MS/T).  This counter starts at 0.)
         */


        idx = isp->sess_isid[5];

        if (iscsiboot_prop && (idx == ISCSI_MAX_CONFIG_SESSIONS)) {
                /*
                 * This is temporary session for boot session propose
                 * no need to bound IP for this session
                 */
                icp->conn_bound = B_FALSE;
                kmem_free(ics, sizeof (iscsi_config_sess_t));
                return (ISCSI_STATUS_SUCCESS);
        }

        if (ics->ics_out <= idx) {
                /*
                 * No longer a configured session.  Return a
                 * failure so we don't attempt to relogin.
                 */
                return (ISCSI_STATUS_SHUTDOWN);
        }

        /*
         * If sessions are unbound set this information on
         * the connection and return success.
         */
        if (ics->ics_bound == B_FALSE) {
                icp->conn_bound = B_FALSE;
                kmem_free(ics, sizeof (iscsi_config_sess_t));
                return (ISCSI_STATUS_SUCCESS);
        }

        /*
         * Since the sessions are bound we need to find the matching
         * binding information for the session's isid.  If this
         * session's isid is > 0 then we need to get more configured
         * session information to find the binding info.
         */
        if (idx > 0) {
                int ics_out;

                ics_out = ics->ics_out;
                /* record new size and free last buffer */
                size = ISCSI_SESSION_CONFIG_SIZE(ics_out);
                kmem_free(ics, sizeof (*ics));

                /* allocate new buffer */
                ics = kmem_zalloc(size, KM_SLEEP);
                ics->ics_in = ics_out;

                /* get configured sessions information */
                if (persistent_get_config_session(name, ics) != B_TRUE) {
                        cmn_err(CE_NOTE, "iscsi session(%d) - "
                            "unable to get configured session information\n",
                            isp->sess_oid);
                        kmem_free(ics, size);
                        return (ISCSI_STATUS_SHUTDOWN);
                }
        }

        /* Copy correct binding information to the connection */
        icp->conn_bound = B_TRUE;
        if (ics->ics_bindings[idx].i_insize == sizeof (struct in_addr)) {
                bcopy(&ics->ics_bindings[idx].i_addr.in4,
                    &icp->conn_bound_addr.sin4.sin_addr.s_addr,
                    sizeof (struct in_addr));
                icp->conn_bound_addr.sin4.sin_family = AF_INET;
        } else {
                bcopy(&ics->ics_bindings[idx].i_addr.in6,
                    &icp->conn_bound_addr.sin6.sin6_addr.s6_addr,
                    sizeof (struct in6_addr));
                icp->conn_bound_addr.sin6.sin6_family = AF_INET6;
        }

        kmem_free(ics, size);

        return (ISCSI_STATUS_SUCCESS);
}

/*
 * +--------------------------------------------------------------------+
 * | Internal Connection Interfaces                                     |
 * +--------------------------------------------------------------------+
 */

/*
 * iscsi_conn_flush_active_cmds - flush all active icmdps
 *      for a connection.
 */
static void
iscsi_conn_flush_active_cmds(iscsi_conn_t *icp)
{
        iscsi_cmd_t     *icmdp;
        iscsi_sess_t    *isp;
        boolean_t       lock_held = B_FALSE;

        ASSERT(icp != NULL);
        isp = icp->conn_sess;
        ASSERT(isp != NULL);

        if (mutex_owned(&icp->conn_queue_active.mutex)) {
                lock_held = B_TRUE;
        } else {
                mutex_enter(&icp->conn_queue_active.mutex);
        }

        /* Flush active queue */
        icmdp = icp->conn_queue_active.head;
        while (icmdp != NULL) {

                mutex_enter(&icmdp->cmd_mutex);
                if (icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) {
                        icmdp->cmd_un.scsi.pkt_stat |= STAT_ABORTED;
                }
                mutex_exit(&icmdp->cmd_mutex);

                iscsi_cmd_state_machine(icmdp,
                    ISCSI_CMD_EVENT_E7, isp);
                icmdp = icp->conn_queue_active.head;
        }

        /* Wait for active queue to drain */
        while (icp->conn_queue_active.count) {
                mutex_exit(&icp->conn_queue_active.mutex);
                delay(drv_usectohz(100000));
                mutex_enter(&icp->conn_queue_active.mutex);
        }

        if (lock_held == B_FALSE) {
                mutex_exit(&icp->conn_queue_active.mutex);
        }

        /* Wait for IDM abort queue to drain (if necessary) */
        mutex_enter(&icp->conn_queue_idm_aborting.mutex);
        while (icp->conn_queue_idm_aborting.count) {
                mutex_exit(&icp->conn_queue_idm_aborting.mutex);
                delay(drv_usectohz(100000));
                mutex_enter(&icp->conn_queue_idm_aborting.mutex);
        }
        mutex_exit(&icp->conn_queue_idm_aborting.mutex);
}

/*
 * iscsi_conn_retry - retry connect/login
 */
void
iscsi_conn_retry(iscsi_sess_t *isp, iscsi_conn_t *icp)
{
        iscsi_task_t *itp;
        uint32_t event_count;

        ASSERT(isp != NULL);
        ASSERT(icp != NULL);

        /* set login min/max time values */
        iscsi_conn_set_login_min_max(icp,
            ISCSI_CONN_DEFAULT_LOGIN_MIN,
            icp->conn_tunable_params.conn_login_max);

        ISCSI_CONN_LOG(CE_NOTE, "DEBUG: iscsi_conn_retry: icp: %p icp: %p ",
            (void *)icp,
            (void *)icp->conn_ic);

        /*
         * Sync base connection information before login.
         * A login redirection might have shifted the
         * current information from the base.
         */
        bcopy(&icp->conn_base_addr, &icp->conn_curr_addr,
            sizeof (icp->conn_curr_addr));

        /* schedule login task */
        itp = kmem_zalloc(sizeof (iscsi_task_t), KM_SLEEP);
        itp->t_arg = icp;
        itp->t_blocking = B_FALSE;
        if (ddi_taskq_dispatch(isp->sess_login_taskq,
            iscsi_login_cb, itp, DDI_SLEEP) !=
            DDI_SUCCESS) {
                kmem_free(itp, sizeof (iscsi_task_t));
                cmn_err(CE_WARN, "iscsi connection(%u) failure - "
                    "unable to schedule login task", icp->conn_oid);

                iscsi_conn_update_state(icp, ISCSI_CONN_STATE_FREE);
                event_count = atomic_inc_32_nv(
                    &isp->sess_state_event_count);
                iscsi_sess_enter_state_zone(isp);

                iscsi_sess_state_machine(isp,
                    ISCSI_SESS_EVENT_N6, event_count);

                iscsi_sess_exit_state_zone(isp);
        }
}

void
iscsi_conn_update_state(iscsi_conn_t *icp, iscsi_conn_state_t next_state)
{
        mutex_enter(&icp->conn_state_mutex);
        (void) iscsi_conn_update_state_locked(icp, next_state);
        mutex_exit(&icp->conn_state_mutex);
}

void
iscsi_conn_update_state_locked(iscsi_conn_t *icp, iscsi_conn_state_t next_state)
{
        ASSERT(mutex_owned(&icp->conn_state_mutex));
        next_state = (next_state > ISCSI_CONN_STATE_MAX) ?
            ISCSI_CONN_STATE_MAX : next_state;
        idm_sm_audit_state_change(&icp->conn_state_audit,
            SAS_ISCSI_CONN, icp->conn_state, next_state);
        switch (next_state) {
        case ISCSI_CONN_STATE_FREE:
        case ISCSI_CONN_STATE_IN_LOGIN:
        case ISCSI_CONN_STATE_LOGGED_IN:
        case ISCSI_CONN_STATE_IN_LOGOUT:
        case ISCSI_CONN_STATE_FAILED:
        case ISCSI_CONN_STATE_POLLING:
                ISCSI_CONN_LOG(CE_NOTE,
                    "iscsi_conn_update_state conn %p %s(%d) -> %s(%d)",
                    (void *)icp,
                    iscsi_ics_name[icp->conn_state], icp->conn_state,
                    iscsi_ics_name[next_state], next_state);
                icp->conn_prev_state = icp->conn_state;
                icp->conn_state = next_state;
                cv_broadcast(&icp->conn_state_change);
                break;
        default:
                cmn_err(CE_WARN, "Update state found illegal state: %x "
                    "prev_state: %x", next_state, icp->conn_prev_state);
                ASSERT(0);
        }
}