root/usr/src/uts/common/io/scsi/adapters/iscsi/iscsi_lun.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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright 2019 Nexenta Systems, Inc.
 * Copyright 2023 Oxide Computer Company
 */

/*
 * iSCSI logical unit interfaces
 */

#include "iscsi.h"
#include <sys/bootprops.h>
#include <sys/sysevent/eventdefs.h>
#include <sys/sysevent/dev.h>

/* tpgt bytes in string form */
#define TPGT_EXT_SIZE   5

/* logical unit number bytes in string form */
#define LUN_EXT_SIZE    10

/*
 * Addition addr size of size of ',' + max str form of tpgt (2 bytes) +
 * ',' + max str form of logical unit number (4 bytes).
 */
#define ADDR_EXT_SIZE   (1 + TPGT_EXT_SIZE + 1 + LUN_EXT_SIZE)

/* internal interfaces */
static iscsi_status_t iscsi_lun_virt_create(iscsi_sess_t *isp,
    uint16_t lun_num, iscsi_lun_t *ilp, struct scsi_inquiry *inq);
static iscsi_status_t iscsi_lun_phys_create(iscsi_sess_t *isp,
    uint16_t lun_num, iscsi_lun_t *ilp, struct scsi_inquiry *inq);

extern dev_info_t       *scsi_vhci_dip;
extern ib_boot_prop_t   *iscsiboot_prop;

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


/*
 * iscsi_lun_create - This function will create a lun mapping.
 * logic specific to MPxIO vs. NDI node creation is switched
 * out to a helper function.
 */
iscsi_status_t
iscsi_lun_create(iscsi_sess_t *isp, uint16_t lun_num, uint8_t lun_addr_type,
    struct scsi_inquiry *inq, char *guid)
{
        iscsi_status_t          rtn             = ISCSI_STATUS_INTERNAL_ERROR;
        iscsi_hba_t             *ihp            = NULL;
        iscsi_lun_t             *ilp            = NULL;
        iscsi_lun_t             *ilp_tmp        = NULL;
        char                    *addr           = NULL;
        uint16_t                boot_lun_num    = 0;
        uint64_t                *lun_num_ptr    = NULL;
        uint32_t                oid_tmp         = 0;

        ASSERT(isp != NULL);
        ihp = isp->sess_hba;
        ASSERT(ihp != NULL);

        mutex_enter(&iscsi_oid_mutex);
        oid_tmp = iscsi_oid++;
        mutex_exit(&iscsi_oid_mutex);

        rw_enter(&isp->sess_lun_list_rwlock, RW_WRITER);
        /*
         * Check whether it has already existed in the list.
         */
        for (ilp_tmp = isp->sess_lun_list; ilp_tmp != NULL;
            ilp_tmp = ilp_tmp->lun_next) {
                if (ilp_tmp->lun_num == lun_num) {
                        /*
                         * The logic unit has already existed in the list,
                         * return with success.
                         */
                        rw_exit(&isp->sess_lun_list_rwlock);
                        return (ISCSI_STATUS_SUCCESS);
                }
        }

        addr = kmem_zalloc((strlen((char *)isp->sess_name) +
            ADDR_EXT_SIZE + 1), KM_SLEEP);
        (void) snprintf(addr,
            (strlen((char *)isp->sess_name) +
            ADDR_EXT_SIZE + 1),
            "%02X%02X%s%04X,%d", isp->sess_isid[4],
            isp->sess_isid[5], isp->sess_name,
            isp->sess_tpgt_nego & 0xFFFF, lun_num);

        /* allocate space for lun struct */
        ilp = kmem_zalloc(sizeof (iscsi_lun_t), KM_SLEEP);
        ilp->lun_sig = ISCSI_SIG_LUN;
        ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
        ilp->lun_state |= ISCSI_LUN_STATE_OFFLINE;

        /* initialize common LU information */
        ilp->lun_num        = lun_num;
        ilp->lun_addr_type  = lun_addr_type;
        ilp->lun_sess       = isp;
        ilp->lun_addr       = addr;
        ilp->lun_type       = inq->inq_dtype & DTYPE_MASK;
        ilp->lun_oid        = oid_tmp;
        /*
         * Setting refcnt to 1 is the first hold for the LUN structure.
         */
        ilp->lun_refcnt     = 1;
        mutex_init(&ilp->lun_mutex, NULL, MUTEX_DRIVER, NULL);

        bcopy(inq->inq_vid, ilp->lun_vid, sizeof (inq->inq_vid));
        bcopy(inq->inq_pid, ilp->lun_pid, sizeof (inq->inq_pid));

        /* store GUID if valid one exists */
        if (guid != NULL) {
                ilp->lun_guid_size = strlen(guid) + 1;
                ilp->lun_guid = kmem_zalloc(ilp->lun_guid_size, KM_SLEEP);
                (void) strcpy(ilp->lun_guid, guid);
        } else {
                ilp->lun_guid_size = 0;
                ilp->lun_guid = NULL;
        }

        /*
         * We need to add the lun to our lists now because during the
         * lun creation we will get called back into multiple times
         * depending on the createion type.  These callbacks will
         * occur via our tran_init_lun, tran_get_name, tran_get_bus_addr,
         * tran_init_pkt, tran_start.
         */
        if (isp->sess_lun_list == NULL) {
                isp->sess_lun_list = ilp;
        } else {
                ilp->lun_next = isp->sess_lun_list;
                isp->sess_lun_list = ilp;
        }

        /* Attempt to create a scsi_vhci binding if GUID is available */
        if ((ihp->hba_mpxio_enabled == B_TRUE) &&
            (guid != NULL)) {
                rtn = iscsi_lun_virt_create(isp, lun_num, ilp, inq);
        }
        if (!ISCSI_SUCCESS(rtn)) {
                /* unable to bind under scsi_vhci, failback to ndi */
                rtn = iscsi_lun_phys_create(isp, lun_num, ilp, inq);
        }

        /*
         * If NOT successful we need to remove the lun from the
         * session and free any related resources.
         */
        if (!ISCSI_SUCCESS(rtn)) {
                if (ilp == isp->sess_lun_list) {
                        /* if head, set head to our next */
                        isp->sess_lun_list = ilp->lun_next;
                } else {
                        /* if not head, set prev lun's next to our next */
                        for (ilp_tmp = isp->sess_lun_list; ilp_tmp;
                            ilp_tmp = ilp_tmp->lun_next) {
                                if (ilp_tmp->lun_next == ilp) {
                                        ilp_tmp->lun_next = ilp->lun_next;
                                        break;
                                }
                        }
                }

                kmem_free(ilp->lun_addr,
                    (strlen((char *)isp->sess_name) +
                    ADDR_EXT_SIZE + 1));
                ilp->lun_addr = NULL;

                if (ilp->lun_guid != NULL) {
                        kmem_free(ilp->lun_guid, ilp->lun_guid_size);
                        ilp->lun_guid = NULL;
                }
                mutex_destroy(&ilp->lun_mutex);
                kmem_free(ilp, sizeof (iscsi_lun_t));
        } else {
                ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
                ilp->lun_state |= ISCSI_LUN_STATE_ONLINE;
                ilp->lun_time_online = ddi_get_time();

                /* Check whether this is the required LUN for iscsi boot */
                if (iscsiboot_prop != NULL && isp->sess_boot == B_TRUE &&
                    iscsiboot_prop->boot_tgt.lun_online == 0) {
                        lun_num_ptr =
                            (uint64_t *)iscsiboot_prop->boot_tgt.tgt_boot_lun;
                        boot_lun_num = (uint16_t)(*lun_num_ptr);
                        if (boot_lun_num == ilp->lun_num) {
                                /*
                                 * During iscsi boot, the boot lun has been
                                 * online, we should set the "online flag".
                                 */
                                iscsiboot_prop->boot_tgt.lun_online = 1;
                        }
                }
        }
        rw_exit(&isp->sess_lun_list_rwlock);

        return (rtn);
}

void
iscsi_lun_hold(iscsi_lun_t *ilp)
{
        mutex_enter(&ilp->lun_mutex);
        /*
         * By design lun_refcnt should never be zero when this routine
         * is called. When the LUN is created the refcnt is set to 1.
         * If iscsi_lun_rele is called and the refcnt goes to zero the
         * structure will be freed so this method shouldn't be called
         * afterwards.
         */
        ASSERT(ilp->lun_refcnt > 0);
        ilp->lun_refcnt++;
        mutex_exit(&ilp->lun_mutex);
}

void
iscsi_lun_rele(iscsi_lun_t *ilp)
{
        ASSERT(ilp != NULL);

        mutex_enter(&ilp->lun_mutex);
        ASSERT(ilp->lun_refcnt > 0);
        if (--ilp->lun_refcnt == 0) {
                iscsi_sess_t            *isp;

                isp = ilp->lun_sess;
                ASSERT(isp != NULL);

                /* ---- release its memory ---- */
                kmem_free(ilp->lun_addr, (strlen((char *)isp->sess_name) +
                    ADDR_EXT_SIZE + 1));

                if (ilp->lun_guid != NULL) {
                        kmem_free(ilp->lun_guid, ilp->lun_guid_size);
                }
                mutex_destroy(&ilp->lun_mutex);
                kmem_free(ilp, sizeof (iscsi_lun_t));
        } else {
                mutex_exit(&ilp->lun_mutex);
        }
}

/*
 * iscsi_lun_cmd_cancel -- as the name implies, cancel all commands for the lun
 *
 * This code is similar to the timeout function with a lot less checking of
 * state before sending the ABORT event for commands on the pending queue.
 *
 * This function is only used by iscsi_lun_destroy().
 */
static void
iscsi_lun_cmd_cancel(iscsi_lun_t *ilp)
{
        iscsi_sess_t    *isp;
        iscsi_cmd_t     *icmdp, *nicmdp;

        isp = ilp->lun_sess;
        rw_enter(&isp->sess_state_rwlock, RW_READER);
        mutex_enter(&isp->sess_queue_pending.mutex);
        for (icmdp = isp->sess_queue_pending.head;
            icmdp; icmdp = nicmdp) {
                nicmdp = icmdp->cmd_next;

                /*
                 * For commands on the pending queue we can go straight
                 * to and abort request which will free the command
                 * and call back to the complete function.
                 */
                iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E4, isp);
        }
        mutex_exit(&isp->sess_queue_pending.mutex);
        rw_exit(&isp->sess_state_rwlock);
}

/*
 * iscsi_lun_destroy - offline and remove lun
 *
 * This interface is called when a name service change has
 * occured and the storage is no longer available to this
 * initiator.  This function will offline and free the
 * solaris node resources.  Then it will free all iscsi lun
 * resources.
 *
 * This function can fail with ISCSI_STATUS_BUSY if the
 * logical unit is in use.  The user should unmount or
 * close the device and perform the nameservice operation
 * again if this occurs.
 */
iscsi_status_t
iscsi_lun_destroy(iscsi_hba_t *ihp, iscsi_lun_t *ilp)
{
        iscsi_status_t          status          = ISCSI_STATUS_SUCCESS;
        iscsi_sess_t            *isp            = NULL;
        iscsi_lun_t             *t_ilp          = NULL;

        ASSERT(ilp != NULL);
        isp = ilp->lun_sess;
        ASSERT(isp != NULL);

        /* flush all outstanding commands first */
        iscsi_lun_cmd_cancel(ilp);

        /* attempt to offline and free solaris node */
        status = iscsi_lun_offline(ihp, ilp, B_TRUE);

        /* If we successfully unplumbed the lun remove it from our lists */
        if (ISCSI_SUCCESS(status)) {
                if (isp->sess_lun_list == ilp) {
                        /* target first item in list */
                        isp->sess_lun_list = ilp->lun_next;
                } else {
                        /*
                         * search session list for ilp pointing
                         * to lun being removed.  Then
                         * update that luns next pointer.
                         */
                        t_ilp = isp->sess_lun_list;
                        while (t_ilp->lun_next != NULL) {
                                if (t_ilp->lun_next == ilp) {
                                        break;
                                }
                                t_ilp = t_ilp->lun_next;
                        }
                        if (t_ilp->lun_next == ilp) {
                                t_ilp->lun_next = ilp->lun_next;
                        } else {
                                /* couldn't find session */
                                ASSERT(FALSE);
                        }
                }

                iscsi_lun_rele(ilp);
        }

        return (status);
}

/*
 * +--------------------------------------------------------------------+
 * | External Logical Unit Interfaces                                   |
 * +--------------------------------------------------------------------+
 */

/*
 * iscsi_lun_virt_create - Creates solaris logical unit via MDI
 */
static iscsi_status_t
iscsi_lun_virt_create(iscsi_sess_t *isp, uint16_t lun_num, iscsi_lun_t *ilp,
    struct scsi_inquiry *inq)
{
        iscsi_status_t          rtn             = ISCSI_STATUS_INTERNAL_ERROR;
        int                     mdi_rtn         = MDI_FAILURE;
        iscsi_hba_t             *ihp            = NULL;
        mdi_pathinfo_t          *pip            = NULL;
        char                    *nodename       = NULL;
        char                    **compatible    = NULL;
        int                     ncompatible     = 0;

        ASSERT(isp != NULL);
        ASSERT(ilp != NULL);
        ihp = isp->sess_hba;
        ASSERT(ihp != NULL);

        /*
         * Generate compatible property
         */
        scsi_hba_nodename_compatible_get(inq, "vhci",
            inq->inq_dtype, NULL, &nodename, &compatible, &ncompatible);

        /* if nodename can't be determined then print a message and skip it */
        if (nodename == NULL) {
                cmn_err(CE_WARN, "iscsi driver found no compatible driver "
                    "for %s lun %d dtype:0x%02x", isp->sess_name, lun_num,
                    inq->inq_dtype);
                return (ISCSI_STATUS_INTERNAL_ERROR);
        }

        /*
         *
         */
        ndi_devi_enter(scsi_vhci_dip);
        mdi_rtn = mdi_pi_alloc_compatible(ihp->hba_dip, nodename,
            ilp->lun_guid, ilp->lun_addr, compatible, ncompatible,
            0, &pip);

        if (mdi_rtn == MDI_SUCCESS) {
                mdi_pi_set_phci_private(pip, (caddr_t)ilp);

                if (mdi_prop_update_string(pip, MDI_GUID,
                    ilp->lun_guid) != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "iscsi driver unable to create "
                            "property for %s lun %d (MDI_GUID)",
                            isp->sess_name, lun_num);
                        mdi_rtn = MDI_FAILURE;
                        goto virt_create_done;
                }

                if (mdi_prop_update_int(pip, TARGET_PROP,
                    isp->sess_oid) != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "iscsi driver unable to create "
                            "property for %s lun %d (TARGET_PROP)",
                            isp->sess_name, lun_num);
                        mdi_rtn = MDI_FAILURE;
                        goto virt_create_done;
                }

                if (mdi_prop_update_int(pip, LUN_PROP,
                    ilp->lun_num) != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "iscsi driver unable to create "
                            "property for %s lun %d (LUN_PROP)",
                            isp->sess_name, lun_num);
                        mdi_rtn = MDI_FAILURE;
                        goto virt_create_done;
                }

                if (mdi_prop_update_string_array(pip, "compatible",
                    compatible, ncompatible) !=
                    DDI_PROP_SUCCESS) {
                        cmn_err(CE_WARN, "iscsi driver unable to create "
                            "property for %s lun %d (COMPATIBLE)",
                            isp->sess_name, lun_num);
                        mdi_rtn = MDI_FAILURE;
                        goto virt_create_done;
                }

                mdi_rtn = mdi_pi_online(pip, 0);
                if (mdi_rtn == MDI_NOT_SUPPORTED) {
                        mdi_rtn = MDI_FAILURE;
                        goto virt_create_done;
                }

                ilp->lun_pip = pip;
                ilp->lun_dip = NULL;

virt_create_done:

                if (pip && mdi_rtn != MDI_SUCCESS) {
                        ilp->lun_pip = NULL;
                        ilp->lun_dip = NULL;
                        (void) mdi_prop_remove(pip, NULL);
                        (void) mdi_pi_free(pip, 0);
                } else {
                        rtn = ISCSI_STATUS_SUCCESS;
                }
        }
        ndi_devi_exit(scsi_vhci_dip);

        scsi_hba_nodename_compatible_free(nodename, compatible);

        return (rtn);
}


/*
 * iscsi_lun_phys_create - creates solaris logical unit via NDI
 */
static iscsi_status_t
iscsi_lun_phys_create(iscsi_sess_t *isp, uint16_t lun_num,
    iscsi_lun_t *ilp, struct scsi_inquiry *inq)
{
        iscsi_status_t          rtn             = ISCSI_STATUS_INTERNAL_ERROR;
        int                     ndi_rtn         = NDI_FAILURE;
        iscsi_hba_t             *ihp            = NULL;
        dev_info_t              *lun_dip        = NULL;
        char                    *nodename       = NULL;
        char                    **compatible    = NULL;
        int                     ncompatible     = 0;
        char                    *scsi_binding_set = NULL;
        char                    instance[32];

        ASSERT(isp != NULL);
        ASSERT(ilp != NULL);
        ihp = isp->sess_hba;
        ASSERT(ihp != NULL);
        ASSERT(inq != NULL);

        /* get the 'scsi-binding-set' property */
        if (ddi_prop_lookup_string(DDI_DEV_T_ANY, isp->sess_hba->hba_dip,
            DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, "scsi-binding-set",
            &scsi_binding_set) != DDI_PROP_SUCCESS) {
                scsi_binding_set = NULL;
        }

        /* generate compatible property */
        scsi_hba_nodename_compatible_get(inq, scsi_binding_set,
            inq->inq_dtype, NULL, &nodename, &compatible, &ncompatible);
        if (scsi_binding_set)
                ddi_prop_free(scsi_binding_set);

        /* if nodename can't be determined then print a message and skip it */
        if (nodename == NULL) {
                cmn_err(CE_WARN, "iscsi driver found no compatible driver "
                    "for %s lun %d", isp->sess_name, lun_num);
                return (ISCSI_STATUS_INTERNAL_ERROR);
        }

        ndi_devi_enter(ihp->hba_dip);

        ndi_rtn = ndi_devi_alloc(ihp->hba_dip, nodename,
            DEVI_SID_NODEID, &lun_dip);

        /* if lun alloc success, set props */
        if (ndi_rtn == NDI_SUCCESS) {

                if (ndi_prop_update_int(DDI_DEV_T_NONE,
                    lun_dip, TARGET_PROP, (int)isp->sess_oid) !=
                    DDI_PROP_SUCCESS) {
                        cmn_err(CE_WARN, "iscsi driver unable to create "
                            "property for %s lun %d (TARGET_PROP)",
                            isp->sess_name, lun_num);
                        ndi_rtn = NDI_FAILURE;
                        goto phys_create_done;
                }

                if (ndi_prop_update_int(DDI_DEV_T_NONE,
                    lun_dip, LUN_PROP, (int)ilp->lun_num) !=
                    DDI_PROP_SUCCESS) {
                        cmn_err(CE_WARN, "iscsi driver unable to create "
                            "property for %s lun %d (LUN_PROP)",
                            isp->sess_name, lun_num);
                        ndi_rtn = NDI_FAILURE;
                        goto phys_create_done;
                }

                if (ndi_prop_update_string_array(DDI_DEV_T_NONE,
                    lun_dip, "compatible", compatible, ncompatible)
                    != DDI_PROP_SUCCESS) {
                        cmn_err(CE_WARN, "iscsi driver unable to create "
                            "property for %s lun %d (COMPATIBLE)",
                            isp->sess_name, lun_num);
                        ndi_rtn = NDI_FAILURE;
                        goto phys_create_done;
                }

phys_create_done:
                /* If props were setup ok, online the lun */
                if (ndi_rtn == NDI_SUCCESS) {
                        /* Try to online the new node */
                        ndi_rtn = ndi_devi_online(lun_dip, 0);
                }

                /* If success set rtn flag, else unwire alloc'd lun */
                if (ndi_rtn == NDI_SUCCESS) {
                        rtn = ISCSI_STATUS_SUCCESS;
                        /*
                         * Assign the instance number for the dev_link
                         * generator.  This will ensure the link name is
                         * unique and persistent across reboots.
                         */
                        (void) snprintf(instance, 32, "%d",
                            ddi_get_instance(lun_dip));
                        (void) ndi_prop_update_string(DDI_DEV_T_NONE,
                            lun_dip, NDI_GUID, instance);
                } else {
                        cmn_err(CE_WARN, "iscsi driver unable to online "
                            "%s lun %d", isp->sess_name, lun_num);
                        ndi_prop_remove_all(lun_dip);
                        (void) ndi_devi_free(lun_dip);
                }

        }
        ndi_devi_exit(ihp->hba_dip);

        ilp->lun_dip = lun_dip;
        ilp->lun_pip = NULL;

        scsi_hba_nodename_compatible_free(nodename, compatible);

        return (rtn);
}


/*
 * iscsi_lun_online - _di_online logical unit
 *
 * This is called after a path has recovered it will cause
 * an offline path to become online/active again.
 */
void
iscsi_lun_online(iscsi_hba_t *ihp, iscsi_lun_t *ilp)
{
        int                     rval            = 0;
        uint64_t                *lun_num_ptr    = NULL;
        uint16_t                boot_lun_num    = 0;
        iscsi_sess_t            *isp            = NULL;
        boolean_t               online          = B_FALSE;
        nvlist_t                *attr_list      = NULL;
        char                    *pathname       = NULL;
        dev_info_t              *lun_dip        = NULL;

        ASSERT(ilp != NULL);
        ASSERT((ilp->lun_pip != NULL) || (ilp->lun_dip != NULL));

        if (ilp->lun_pip != NULL) {
                ndi_devi_enter(scsi_vhci_dip);
                rval =  mdi_pi_online(ilp->lun_pip, 0);
                ndi_devi_exit(scsi_vhci_dip);
                if (rval == MDI_SUCCESS) {
                        ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
                        ilp->lun_state |= ISCSI_LUN_STATE_ONLINE;
                        ilp->lun_time_online = ddi_get_time();
                        online = B_TRUE;
                }

        } else if (ilp->lun_dip != NULL) {
                ndi_devi_enter(ihp->hba_dip);
                rval =  ndi_devi_online(ilp->lun_dip, 0);
                ndi_devi_exit(ihp->hba_dip);
                if (rval == NDI_SUCCESS) {
                        ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
                        ilp->lun_state |= ISCSI_LUN_STATE_ONLINE;
                        ilp->lun_time_online = ddi_get_time();
                        online = B_TRUE;
                }
        }

        /* Check whether this is the required LUN for iscsi boot */
        if (iscsiboot_prop != NULL &&
            iscsiboot_prop->boot_tgt.lun_online == 0) {
                isp = ilp->lun_sess;
                if (isp->sess_boot == B_TRUE) {
                        lun_num_ptr =
                            (uint64_t *)iscsiboot_prop->boot_tgt.tgt_boot_lun;
                        boot_lun_num = (uint16_t)(*lun_num_ptr);
                        if (boot_lun_num == ilp->lun_num) {
                                /*
                                 * During iscsi boot, the boot lun has been
                                 * online, we should set the "online flag".
                                 */
                                iscsiboot_prop->boot_tgt.lun_online = 1;
                        }
                }
        }

        /*
         * If the LUN has been online and it is a disk,
         * send out a system event.
         */
        if (online == B_TRUE && ilp->lun_type == DTYPE_DIRECT) {
                if (nvlist_alloc(&attr_list, NV_UNIQUE_NAME_TYPE, KM_SLEEP) !=
                    DDI_SUCCESS) {
                        return;
                }

                if (ilp->lun_pip != NULL) {
                        lun_dip = mdi_pi_get_client(ilp->lun_pip);
                } else {
                        lun_dip = ilp->lun_dip;
                }

                pathname = kmem_zalloc(MAXNAMELEN + 1, KM_SLEEP);
                (void) ddi_pathname(lun_dip, pathname);

                if (nvlist_add_string(attr_list, DEV_PHYS_PATH, pathname) !=
                    DDI_SUCCESS) {
                        nvlist_free(attr_list);
                        kmem_free(pathname, MAXNAMELEN + 1);
                        return;
                }
                iscsi_send_sysevent(ihp, EC_DEV_ADD, ESC_DISK, attr_list);
                kmem_free(pathname, MAXNAMELEN + 1);
                nvlist_free(attr_list);
        }
}

/*
 * iscsi_lun_offline - attempt _di_offline [and optional _di_free]
 *
 * This function is called via two paths.  When a transport
 * path has failed it will be called to offline the logical
 * unit.  When nameservice access has been removed it will
 * be called to both offline and free the logical unit.
 * (This operates soley on the solaris node states.
 * iscsi_lun_destroy() should be called when attempting
 * to free all iscsi lun resources.)
 *
 * This function can fail with ISCSI_STATUS_BUSY if the
 * logical unit is in use.  The user should unmount or
 * close the device and perform the nameservice operation
 * again if this occurs.
 *
 * If we fail to offline a LUN that we don't want to destroy,
 * we will mark it with invalid state. If this LUN still
 * exists on the target, we can have another chance to online
 * it again when we do the LUN enumeration.
 */
iscsi_status_t
iscsi_lun_offline(iscsi_hba_t *ihp, iscsi_lun_t *ilp, boolean_t lun_free)
{
        iscsi_status_t          status          = ISCSI_STATUS_SUCCESS;
        dev_info_t              *cdip;
        char                    *pathname       = NULL;
        boolean_t               offline         = B_FALSE;
        nvlist_t                *attr_list      = NULL;

        ASSERT(ilp != NULL);
        ASSERT((ilp->lun_pip != NULL) || (ilp->lun_dip != NULL));

        if (ilp->lun_pip == NULL)
                cdip = ilp->lun_dip;
        else
                cdip = mdi_pi_get_client(ilp->lun_pip);

        if (cdip != NULL && ilp->lun_type == DTYPE_DIRECT) {
                pathname = kmem_zalloc(MAXNAMELEN + 1, KM_SLEEP);
                (void) ddi_pathname(cdip, pathname);
        }

        /* Attempt to offline the logical units */
        if (ilp->lun_pip != NULL) {
                /* virt/mdi */
                ndi_devi_enter(scsi_vhci_dip);
                if (mdi_pi_offline(ilp->lun_pip, 0) == MDI_SUCCESS) {
                        ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
                        ilp->lun_state |= ISCSI_LUN_STATE_OFFLINE;
                        if (lun_free == B_TRUE) {
                                (void) mdi_prop_remove(ilp->lun_pip, NULL);
                                (void) mdi_pi_free(ilp->lun_pip, 0);
                        }
                        offline = B_TRUE;
                } else {
                        status = ISCSI_STATUS_BUSY;
                        if (lun_free == B_FALSE) {
                                ilp->lun_state |= ISCSI_LUN_STATE_INVALID;
                                offline = B_TRUE;
                        }
                }
                ndi_devi_exit(scsi_vhci_dip);

        } else  {
                /* phys/ndi */
                int flags = NDI_DEVFS_CLEAN;

                ndi_devi_enter(ihp->hba_dip);
                if (lun_free == B_TRUE &&
                    (ilp->lun_state & ISCSI_LUN_STATE_ONLINE))
                        flags |= NDI_DEVI_REMOVE;
                if (ndi_devi_offline(ilp->lun_dip, flags) != NDI_SUCCESS) {
                        status = ISCSI_STATUS_BUSY;
                        if (lun_free == B_FALSE) {
                                ilp->lun_state |= ISCSI_LUN_STATE_INVALID;
                                offline = B_TRUE;
                        }
                } else {
                        ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
                        ilp->lun_state |= ISCSI_LUN_STATE_OFFLINE;
                        offline = B_TRUE;
                }
                ndi_devi_exit(ihp->hba_dip);
        }

        if (offline == B_TRUE && pathname != NULL &&
            ilp->lun_type == DTYPE_DIRECT) {
                if (nvlist_alloc(&attr_list, NV_UNIQUE_NAME_TYPE, KM_SLEEP) !=
                    DDI_SUCCESS) {
                        kmem_free(pathname, MAXNAMELEN + 1);
                        return (status);
                }

                if (nvlist_add_string(attr_list, DEV_PHYS_PATH, pathname) !=
                    DDI_SUCCESS) {
                        nvlist_free(attr_list);
                        kmem_free(pathname, MAXNAMELEN + 1);
                        return (status);
                }

                iscsi_send_sysevent(ihp, EC_DEV_REMOVE, ESC_DISK, attr_list);
                nvlist_free(attr_list);
        }

        if (pathname != NULL) {
                kmem_free(pathname, MAXNAMELEN + 1);
        }

        return (status);
}