/* * 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 session interfaces */ #include #include "iscsi.h" #include "persistent.h" #include "iscsi_targetparam.h" #define ISCSI_SESS_ENUM_TIMEOUT_DEFAULT 60 #define SCSI_INQUIRY_PQUAL_MASK 0xE0 boolean_t iscsi_sess_logging = B_FALSE; /* * used to store report lun information found * * lun_valid: if TRUE means the entry contains a valid entry * lun_found: if TRUE means the lun has been found in the sess_lun_list * lun_num: contains the lun_number * lun_addr_type: indicates lun's type of addressing */ typedef struct replun_data { boolean_t lun_valid; boolean_t lun_found; uint16_t lun_num; uint8_t lun_addr_type; } replun_data_t; int iscsi_sess_enum_timeout = ISCSI_SESS_ENUM_TIMEOUT_DEFAULT; /* * The following private tunable, settable via * set iscsi:iscsi_sess_max_delay = 64 * in /etc/system, provides customer relief for configurations max interval in * seconds of retry for a unreachable target during the login. */ int iscsi_sess_max_delay = ISCSI_DEFAULT_MAX_STORM_DELAY; /* * Warning messages for the session scsi enumeration */ static const char *iscsi_sess_enum_warn_msgs[] = { "completed", "partially successful", "IO failures", "submitted", "unable to submit the enumeration", "session is gone", "test unit ready failed" }; /* internal interfaces */ /* LINTED E_STATIC_UNUSED */ static iscsi_sess_t *iscsi_sess_alloc(iscsi_hba_t *ihp, iscsi_sess_type_t type); static char *iscsi_sess_event_str(iscsi_sess_event_t event); static iscsi_status_t iscsi_sess_threads_create(iscsi_sess_t *isp); static void iscsi_sess_flush(iscsi_sess_t *isp); static void iscsi_sess_offline_luns(iscsi_sess_t *isp); static iscsi_status_t retrieve_lundata(uint32_t lun_count, unsigned char *buf, iscsi_sess_t *isp, uint16_t *lun_data, uint8_t *lun_addr_type); /* internal state machine interfaces */ static void iscsi_sess_state_free(iscsi_sess_t *isp, iscsi_sess_event_t event, uint32_t event_count); static void iscsi_sess_state_logged_in(iscsi_sess_t *isp, iscsi_sess_event_t event, uint32_t event_count); static void iscsi_sess_state_failed(iscsi_sess_t *isp, iscsi_sess_event_t event, uint32_t event_count); static void iscsi_sess_state_in_flush(iscsi_sess_t *isp, iscsi_sess_event_t event, uint32_t event_count); static void iscsi_sess_state_flushed(iscsi_sess_t *isp, iscsi_sess_event_t event, uint32_t event_count); /* internal enumeration interfaces */ static void iscsi_sess_enumeration(void *arg); static iscsi_status_t iscsi_sess_testunitready(iscsi_sess_t *isp, uint32_t event_count); static iscsi_status_t iscsi_sess_reportluns(iscsi_sess_t *isp, uint32_t event_count); static void iscsi_sess_inquiry(iscsi_sess_t *isp, uint16_t lun_num, uint8_t lun_addr_type, uint32_t event_count, iscsi_lun_t *ilp); static void iscsi_sess_update_busy_luns(iscsi_sess_t *isp, boolean_t clear); static void iscsi_sess_enum_warn(iscsi_sess_t *isp, iscsi_enum_result_t r); /* * +--------------------------------------------------------------------+ * | External Session Interfaces | * +--------------------------------------------------------------------+ */ iscsi_sess_t * iscsi_sess_create(iscsi_hba_t *ihp, iSCSIDiscoveryMethod_t method, struct sockaddr *addr_dsc, char *target_name, int tpgt, uchar_t isid_lsb, iscsi_sess_type_t type, uint32_t *oid) { iscsi_sess_t *isp = NULL; int len = 0; char *tq_name; char *th_name; iscsi_status_t status; len = strlen(target_name); clean_failed_sess: if (isp != NULL) { (void) iscsi_sess_destroy(isp); } for (isp = ihp->hba_sess_list; isp; isp = isp->sess_next) { /* Match target name and LSB ISID */ if ((strcmp((char *)isp->sess_name, target_name) == 0) && (isp->sess_isid[5] == isid_lsb)) { /* Match TPGT */ if (isp->sess_tpgt_conf == tpgt) { /* Found mathing session, return oid/ptr */ *oid = isp->sess_oid; if (isp->sess_wd_thread != NULL && isp->sess_ic_thread != NULL) { return (isp); } if (isp->sess_wd_thread == NULL) { /* * Under rare cases wd thread is already * freed, create it if so. */ th_name = kmem_zalloc( ISCSI_TH_MAX_NAME_LEN, KM_SLEEP); if (snprintf(th_name, (ISCSI_TH_MAX_NAME_LEN - 1), ISCSI_SESS_WD_NAME_FORMAT, ihp->hba_oid, isp->sess_oid) < ISCSI_TH_MAX_NAME_LEN) { isp->sess_wd_thread = iscsi_thread_create( ihp->hba_dip, th_name, iscsi_wd_thread, isp); (void) iscsi_thread_start( isp->sess_wd_thread); } kmem_free(th_name, ISCSI_TH_MAX_NAME_LEN); if (isp->sess_wd_thread == NULL) { /* No way to save it */ goto clean_failed_sess; } } if (isp->sess_ic_thread == NULL) { status = iscsi_sess_threads_create(isp); if (status != ISCSI_STATUS_SUCCESS) { goto clean_failed_sess; } } return (isp); } /* * Also protect against creating duplicate * sessions with different configured tpgt * values. default vs. defined. */ if ((((isp->sess_tpgt_conf == ISCSI_DEFAULT_TPGT) && (tpgt != ISCSI_DEFAULT_TPGT)) || ((isp->sess_tpgt_conf != ISCSI_DEFAULT_TPGT) && (tpgt == ISCSI_DEFAULT_TPGT)))) { /* Dangerous configuration. Fail Request */ return (NULL); } } } isp = (iscsi_sess_t *)kmem_zalloc(sizeof (iscsi_sess_t), KM_SLEEP); /* * If this session is not a Send Targets session, set the target * that this session is associated with. */ if (strncmp(target_name, SENDTARGETS_DISCOVERY, strlen(SENDTARGETS_DISCOVERY))) { isp->sess_target_oid = iscsi_targetparam_get_oid( (uchar_t *)target_name); } if (method & iSCSIDiscoveryMethodBoot) { /* This is boot session. */ isp->sess_boot = B_TRUE; } else { isp->sess_boot = B_FALSE; } /* Associate session with this discovery method */ method = method & ~(iSCSIDiscoveryMethodBoot); isp->sess_discovered_by = method; if (addr_dsc == NULL) { bzero(&isp->sess_discovered_addr, sizeof (isp->sess_discovered_addr)); } else { bcopy(addr_dsc, &isp->sess_discovered_addr, SIZEOF_SOCKADDR(addr_dsc)); } /* assign unique key for the session */ mutex_enter(&iscsi_oid_mutex); isp->sess_oid = iscsi_oid++; *oid = isp->sess_oid; mutex_exit(&iscsi_oid_mutex); /* setup session parameters */ isp->sess_name_length = 0; isp->sess_sig = ISCSI_SIG_SESS; isp->sess_state = ISCSI_SESS_STATE_FREE; rw_init(&isp->sess_state_rwlock, NULL, RW_DRIVER, NULL); mutex_init(&isp->sess_reset_mutex, NULL, MUTEX_DRIVER, NULL); isp->sess_hba = ihp; isp->sess_isid[0] = ISCSI_SUN_ISID_0; isp->sess_isid[1] = ISCSI_SUN_ISID_1; isp->sess_isid[2] = ISCSI_SUN_ISID_2; isp->sess_isid[3] = ISCSI_SUN_ISID_3; isp->sess_isid[4] = 0; isp->sess_isid[5] = isid_lsb; isp->sess_cmdsn = 1; isp->sess_expcmdsn = 1; isp->sess_maxcmdsn = 1; isp->sess_last_err = NoError; isp->sess_tsid = 0; isp->sess_type = type; isp->sess_reset_in_progress = B_FALSE; isp->sess_boot_nic_reset = B_FALSE; idm_sm_audit_init(&isp->sess_state_audit); /* copy default driver login parameters */ bcopy(&ihp->hba_params, &isp->sess_params, sizeof (iscsi_login_params_t)); /* copy target name into session */ bcopy((char *)target_name, isp->sess_name, len); isp->sess_name_length = len; isp->sess_tpgt_conf = tpgt; isp->sess_tpgt_nego = ISCSI_DEFAULT_TPGT; /* initialize pending and completion queues */ iscsi_init_queue(&isp->sess_queue_pending); iscsi_init_queue(&isp->sess_queue_completion); /* setup sessions lun list */ isp->sess_lun_list = NULL; rw_init(&isp->sess_lun_list_rwlock, NULL, RW_DRIVER, NULL); /* setup sessions connection list */ isp->sess_conn_act = NULL; isp->sess_conn_list = NULL; rw_init(&isp->sess_conn_list_rwlock, NULL, RW_DRIVER, NULL); mutex_init(&isp->sess_cmdsn_mutex, NULL, MUTEX_DRIVER, NULL); /* create the session task queue */ tq_name = kmem_zalloc(ISCSI_TH_MAX_NAME_LEN, KM_SLEEP); if (snprintf(tq_name, (ISCSI_TH_MAX_NAME_LEN - 1), ISCSI_SESS_LOGIN_TASKQ_NAME_FORMAT, ihp->hba_oid, isp->sess_oid) < ISCSI_TH_MAX_NAME_LEN) { isp->sess_login_taskq = ddi_taskq_create(ihp->hba_dip, tq_name, 1, TASKQ_DEFAULTPRI, 0); } if (isp->sess_login_taskq == NULL) { kmem_free(tq_name, ISCSI_TH_MAX_NAME_LEN); goto iscsi_sess_cleanup2; } if (snprintf(tq_name, (ISCSI_TH_MAX_NAME_LEN - 1), ISCSI_SESS_ENUM_TASKQ_NAME_FORMAT, ihp->hba_oid, isp->sess_oid) < ISCSI_TH_MAX_NAME_LEN) { isp->sess_enum_taskq = ddi_taskq_create(ihp->hba_dip, tq_name, 1, TASKQ_DEFAULTPRI, 0); } kmem_free(tq_name, ISCSI_TH_MAX_NAME_LEN); if (isp->sess_enum_taskq == NULL) { goto iscsi_sess_cleanup1; } /* startup watchdog */ th_name = kmem_zalloc(ISCSI_TH_MAX_NAME_LEN, KM_SLEEP); if (snprintf(th_name, (ISCSI_TH_MAX_NAME_LEN - 1), ISCSI_SESS_WD_NAME_FORMAT, ihp->hba_oid, isp->sess_oid) < ISCSI_TH_MAX_NAME_LEN) { isp->sess_wd_thread = iscsi_thread_create(ihp->hba_dip, th_name, iscsi_wd_thread, isp); (void) iscsi_thread_start(isp->sess_wd_thread); } kmem_free(th_name, ISCSI_TH_MAX_NAME_LEN); if (isp->sess_wd_thread == NULL) { goto iscsi_sess_cleanup0; } status = iscsi_sess_threads_create(isp); if (status != ISCSI_STATUS_SUCCESS) { goto iscsi_sess_cleanup1; } /* Add new target to the hba target list */ if (ihp->hba_sess_list == NULL) { ihp->hba_sess_list = isp; } else { isp->sess_next = ihp->hba_sess_list; ihp->hba_sess_list = isp; } KSTAT_INC_HBA_CNTR_SESS(ihp); (void) iscsi_sess_kstat_init(isp); if (type == ISCSI_SESS_TYPE_NORMAL) { isp->sess_enum_status = ISCSI_SESS_ENUM_FREE; isp->sess_enum_result = ISCSI_SESS_ENUM_COMPLETE; isp->sess_enum_result_count = 0; mutex_init(&isp->sess_enum_lock, NULL, MUTEX_DRIVER, NULL); cv_init(&isp->sess_enum_cv, NULL, CV_DRIVER, NULL); } mutex_init(&isp->sess_state_wmutex, NULL, MUTEX_DRIVER, NULL); cv_init(&isp->sess_state_wcv, NULL, CV_DRIVER, NULL); isp->sess_state_hasw = B_FALSE; isp->sess_state_event_count = 0; return (isp); iscsi_sess_cleanup0: ddi_taskq_destroy(isp->sess_enum_taskq); iscsi_sess_cleanup1: ddi_taskq_destroy(isp->sess_login_taskq); iscsi_sess_cleanup2: if (isp->sess_wd_thread != NULL) { iscsi_thread_destroy(isp->sess_wd_thread); isp->sess_wd_thread = NULL; } if (isp->sess_ic_thread != NULL) { iscsi_thread_destroy(isp->sess_ic_thread); isp->sess_ic_thread = NULL; } mutex_destroy(&isp->sess_cmdsn_mutex); rw_destroy(&isp->sess_conn_list_rwlock); rw_destroy(&isp->sess_lun_list_rwlock); iscsi_destroy_queue(&isp->sess_queue_completion); iscsi_destroy_queue(&isp->sess_queue_pending); rw_destroy(&isp->sess_state_rwlock); mutex_destroy(&isp->sess_reset_mutex); kmem_free(isp, sizeof (iscsi_sess_t)); return (NULL); } /* * iscsi_sess_get - return the session structure for based on a * passed in oid and hba instance. */ int iscsi_sess_get(uint32_t oid, iscsi_hba_t *ihp, iscsi_sess_t **ispp) { int rval = 0; iscsi_sess_t *isp = NULL; ASSERT(ihp != NULL); ASSERT(ispp != NULL); /* See if we already created this session */ for (isp = ihp->hba_sess_list; isp; isp = isp->sess_next) { /* compare target name as the unique identifier */ if (isp->sess_oid == oid) { /* Found matching session */ break; } } /* If not null this session is already available */ if (isp != NULL) { /* Existing session, return it */ *ispp = isp; } else { rval = EFAULT; } return (rval); } /* * iscsi_sess_online - initiate online of sessions connections */ void iscsi_sess_online(void *arg) { iscsi_sess_t *isp; iscsi_hba_t *ihp; iscsi_conn_t *icp; int idx; uint32_t event_count; isp = (iscsi_sess_t *)arg; ASSERT(isp != NULL); ihp = isp->sess_hba; ASSERT(ihp != NULL); /* * Stale /dev links can cause us to get floods * of config requests. To prevent these repeated * requests from causing unneeded login to the * unreachable target, we won't try it during * the delay. */ if (ddi_get_lbolt() < isp->sess_failure_lbolt + SEC_TO_TICK(isp->sess_storm_delay)) { return; } /* * Perform a crude version of round robin to * determine which connection to use for * this session. Since byte 5 in session ID * is overridden for full feature session, * the connection to be selected depends on * the result of sess_isid[5] devided by the * next connection ID. * If MS/T is enabled and there are multiple * IPs are available on the target, we can * select different IPs to connect in this * way. */ icp = isp->sess_conn_act; if (icp == NULL) { icp = isp->sess_conn_list; for (idx = 0; idx < (isp->sess_isid[5] % isp->sess_conn_next_cid); idx++) { ASSERT(icp->conn_next != NULL); icp = icp->conn_next; } isp->sess_conn_act = icp; } if (icp == NULL) { cmn_err(CE_NOTE, "iscsi session(%d) - " "no connection assigned", isp->sess_oid); return; } /* * If connection is in free state, start * login. If already logged in, try to * re-enumerate LUs on the session. */ mutex_enter(&icp->conn_state_mutex); if (icp->conn_state == ISCSI_CONN_STATE_FREE) { /* * attempt to login into the first connection in our connection * list. If this fails, we will try the next connection * in our list until end of the list. */ while (icp != NULL) { if (iscsi_conn_online(icp) == ISCSI_STATUS_SUCCESS) { mutex_exit(&icp->conn_state_mutex); break; } else { mutex_exit(&icp->conn_state_mutex); icp = icp->conn_next; if (icp != NULL) { mutex_enter(&icp->conn_state_mutex); } } } isp->sess_conn_act = icp; if (icp == NULL) { /* the target for this session is unreachable */ isp->sess_failure_lbolt = ddi_get_lbolt(); if (isp->sess_storm_delay == 0) { isp->sess_storm_delay++; } else { if ((isp->sess_storm_delay * 2) < iscsi_sess_max_delay) { isp->sess_storm_delay = isp->sess_storm_delay * 2; } else { isp->sess_storm_delay = iscsi_sess_max_delay; } } } else { isp->sess_storm_delay = 0; isp->sess_failure_lbolt = 0; } } else if (icp->conn_state == ISCSI_CONN_STATE_LOGGED_IN) { 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_N1, event_count); iscsi_sess_exit_state_zone(isp); } else { mutex_exit(&icp->conn_state_mutex); } } /* * iscsi_sess_destroy - Destroys a iscsi session structure * and de-associates it from the hba. */ iscsi_status_t iscsi_sess_destroy(iscsi_sess_t *isp) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_status_t tmprval = ISCSI_STATUS_SUCCESS; iscsi_hba_t *ihp; iscsi_sess_t *t_isp; iscsi_lun_t *ilp; iscsi_conn_t *icp; ASSERT(isp != NULL); ihp = isp->sess_hba; ASSERT(ihp != NULL); /* * The first step in tearing down the session * has to be offlining all the LUNs. This will * ensure there is no outstanding IO by upper * level drivers. If this fails then we are * unable to destroy the session. * * Try all luns and continue upon failure * to remove what is removable before returning * the last error. */ rw_enter(&isp->sess_lun_list_rwlock, RW_WRITER); ilp = isp->sess_lun_list; while (ilp != NULL) { iscsi_lun_t *ilp_next = ilp->lun_next; tmprval = iscsi_lun_destroy(ihp, ilp); if (!ISCSI_SUCCESS(tmprval)) { rval = tmprval; } ilp = ilp_next; } rw_exit(&isp->sess_lun_list_rwlock); if (!ISCSI_SUCCESS(rval)) { return (rval); } /* The next step is to logout of the connections. */ rw_enter(&isp->sess_conn_list_rwlock, RW_WRITER); icp = isp->sess_conn_list; while (icp != NULL) { rval = iscsi_conn_offline(icp); if (ISCSI_SUCCESS(rval)) { /* Succes, Continue processing... */ icp = icp->conn_next; } else { /* Failure, Stop processing... */ rw_exit(&isp->sess_conn_list_rwlock); return (rval); } } rw_exit(&isp->sess_conn_list_rwlock); /* * At this point all connections should be in * a FREE state which will have pushed the session * to a FREE state. */ ASSERT(isp->sess_state == ISCSI_SESS_STATE_FREE || isp->sess_state == ISCSI_SESS_STATE_FAILED); /* Stop watchdog before destroying connections */ if (isp->sess_wd_thread) { iscsi_thread_destroy(isp->sess_wd_thread); isp->sess_wd_thread = NULL; } /* Destroy connections */ rw_enter(&isp->sess_conn_list_rwlock, RW_WRITER); icp = isp->sess_conn_list; while (icp != NULL) { rval = iscsi_conn_destroy(icp); if (!ISCSI_SUCCESS(rval)) { rw_exit(&isp->sess_conn_list_rwlock); return (rval); } icp = isp->sess_conn_list; } rw_exit(&isp->sess_conn_list_rwlock); /* Destroy Session ic thread */ if (isp->sess_ic_thread != NULL) { iscsi_thread_destroy(isp->sess_ic_thread); isp->sess_ic_thread = NULL; } /* Destroy session task queue */ ddi_taskq_destroy(isp->sess_enum_taskq); ddi_taskq_destroy(isp->sess_login_taskq); /* destroy pending and completion queues */ iscsi_destroy_queue(&isp->sess_queue_pending); iscsi_destroy_queue(&isp->sess_queue_completion); /* Remove session from ihp */ if (ihp->hba_sess_list == isp) { /* session first item in list */ ihp->hba_sess_list = isp->sess_next; } else { /* * search hba list for isp pointing * to session being removed. Then * update that sessions next pointer. */ t_isp = ihp->hba_sess_list; while (t_isp->sess_next != NULL) { if (t_isp->sess_next == isp) { break; } t_isp = t_isp->sess_next; } if (t_isp->sess_next == isp) { t_isp->sess_next = isp->sess_next; } else { /* couldn't find session */ ASSERT(FALSE); } } if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) { /* Wait for all enum requests complete */ mutex_enter(&isp->sess_enum_lock); while (isp->sess_enum_result_count > 0) { cv_wait(&isp->sess_enum_cv, &isp->sess_enum_lock); } mutex_exit(&isp->sess_enum_lock); } /* Destroy this Sessions Data */ (void) iscsi_sess_kstat_term(isp); rw_destroy(&isp->sess_lun_list_rwlock); rw_destroy(&isp->sess_conn_list_rwlock); mutex_destroy(&isp->sess_cmdsn_mutex); rw_destroy(&isp->sess_state_rwlock); mutex_destroy(&isp->sess_reset_mutex); if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) { mutex_destroy(&isp->sess_enum_lock); cv_destroy(&isp->sess_enum_cv); } mutex_destroy(&isp->sess_state_wmutex); cv_destroy(&isp->sess_state_wcv); kmem_free(isp, sizeof (iscsi_sess_t)); return (rval); } extern ib_boot_prop_t *iscsiboot_prop; /* * static iscsi_sess_set_auth - * */ boolean_t iscsi_sess_set_auth(iscsi_sess_t *isp) { char *init_name; iscsi_chap_props_t *chap = NULL; iscsi_auth_props_t *auth = NULL; uchar_t *tmp = NULL; if (isp == (iscsi_sess_t *)NULL) { return (B_FALSE); } /* Obtain initiator's name */ if (isp->sess_hba == (iscsi_hba_t *)NULL) { return (B_FALSE); } init_name = (char *)isp->sess_hba->hba_name; /* Zero out the session authentication structure */ bzero(&isp->sess_auth, sizeof (iscsi_auth_t)); if (isp->sess_boot == B_FALSE) { auth = (iscsi_auth_props_t *)kmem_zalloc (sizeof (iscsi_auth_props_t), KM_SLEEP); /* Obtain target's authentication settings. */ if (persistent_auth_get((char *)isp->sess_name, auth) != B_TRUE) { /* * If no target authentication settings found, * try to obtain system wide configuration * (from the initiator). */ bzero(auth, sizeof (*auth)); if (persistent_auth_get(init_name, auth) != B_TRUE) { bzero(auth, sizeof (*auth)); auth->a_auth_method = authMethodNone; } /* * We do not support system wide bi-directional * auth flag. */ auth->a_bi_auth = B_FALSE; } chap = (iscsi_chap_props_t *)kmem_zalloc (sizeof (iscsi_chap_props_t), KM_SLEEP); /* * Initialize the target-side chap name to the session name * if no chap settings have been saved for the current session. */ if (persistent_chap_get((char *)isp->sess_name, chap) == B_FALSE) { int name_len = strlen((char *)isp->sess_name); bcopy((char *)isp->sess_name, chap->c_user, name_len); chap->c_user_len = name_len; (void) (persistent_chap_set((char *)isp->sess_name, chap)); bzero(chap, sizeof (*chap)); } if (auth->a_auth_method & authMethodCHAP) { /* Obtain initiator's CHAP settings. */ if (persistent_chap_get(init_name, chap) == B_FALSE) { /* No initiator secret defined. */ kmem_free(chap, sizeof (iscsi_chap_props_t)); /* Set authentication method to NONE */ isp->sess_auth.password_length = 0; kmem_free(auth, sizeof (iscsi_auth_props_t)); return (B_FALSE); } bcopy(chap->c_user, isp->sess_auth.username, sizeof (chap->c_user)); bcopy(chap->c_secret, isp->sess_auth.password, sizeof (chap->c_secret)); isp->sess_auth.password_length = chap->c_secret_len; } else { /* Set authentication method to NONE */ isp->sess_auth.password_length = 0; } /* * Consider enabling bidirectional authentication only if * authentication method is not NONE. */ if (auth->a_auth_method & authMethodCHAP && auth->a_bi_auth == B_TRUE) { /* Enable bi-directional authentication. */ isp->sess_auth.bidirectional_auth = 1; bzero(chap, sizeof (*chap)); /* Obtain target's CHAP settings. */ if (persistent_chap_get((char *)isp->sess_name, chap) == B_TRUE) { bcopy(chap->c_secret, isp->sess_auth.password_in, sizeof (chap->c_secret)); bcopy(chap->c_user, isp->sess_auth.username_in, strlen((char *)chap->c_user)); isp->sess_auth.password_length_in = chap->c_secret_len; } else { /* * No target secret defined. * RADIUS server should have been enabled. */ /* EMPTY */ } } else { /* Disable bi-directional authentication */ isp->sess_auth.bidirectional_auth = 0; } if (auth != NULL) { kmem_free(auth, sizeof (iscsi_auth_props_t)); } if (chap != NULL) { kmem_free(chap, sizeof (iscsi_chap_props_t)); } } else { /* * This session is boot session. We will use the CHAP and * the user name got from the boot property structure instead * of persistent sotre. */ if (iscsiboot_prop == NULL) { return (B_FALSE); } if (iscsiboot_prop->boot_init.ini_chap_sec == NULL) { return (B_FALSE); } /* CHAP secret */ (void) bcopy(iscsiboot_prop->boot_init.ini_chap_sec, isp->sess_auth.password, strlen((char *)iscsiboot_prop->boot_init.ini_chap_sec)); /* * If chap name is not set, * we will use initiator name instead. */ if (iscsiboot_prop->boot_init.ini_chap_name == NULL) { (void) bcopy(init_name, isp->sess_auth.username, strlen(init_name)); } else { tmp = iscsiboot_prop->boot_init.ini_chap_name; (void) bcopy(tmp, isp->sess_auth.username, strlen((char *)tmp)); } isp->sess_auth.password_length = strlen((char *)iscsiboot_prop->boot_init.ini_chap_sec); if (iscsiboot_prop->boot_tgt.tgt_chap_sec != NULL) { /* * Bidirectional authentication is required. */ tmp = iscsiboot_prop->boot_tgt.tgt_chap_sec; (void) bcopy(tmp, isp->sess_auth.password_in, strlen((char *)tmp)); /* * If the target's chap name is not set, we will use * session name instead. */ if (iscsiboot_prop->boot_tgt.tgt_chap_name == NULL) { (void) bcopy(isp->sess_name, isp->sess_auth.username_in, isp->sess_name_length); } else { tmp = iscsiboot_prop->boot_tgt.tgt_chap_name; (void) bcopy(tmp, isp->sess_auth.username_in, strlen((char *)tmp)); } tmp = iscsiboot_prop->boot_tgt.tgt_chap_sec; isp->sess_auth.password_length_in = strlen((char *)tmp); isp->sess_auth.bidirectional_auth = 1; } } /* Set up authentication buffers only if configured */ if ((isp->sess_auth.password_length != 0) || (isp->sess_auth.password_length_in != 0)) { isp->sess_auth.num_auth_buffers = 5; isp->sess_auth.auth_buffers[0].address = &(isp->sess_auth.auth_client_block); isp->sess_auth.auth_buffers[0].length = sizeof (isp->sess_auth.auth_client_block); isp->sess_auth.auth_buffers[1].address = &(isp->sess_auth.auth_recv_string_block); isp->sess_auth.auth_buffers[1].length = sizeof (isp->sess_auth.auth_recv_string_block); isp->sess_auth.auth_buffers[2].address = &(isp->sess_auth.auth_send_string_block); isp->sess_auth.auth_buffers[2].length = sizeof (isp->sess_auth.auth_send_string_block); isp->sess_auth.auth_buffers[3].address = &(isp->sess_auth.auth_recv_binary_block); isp->sess_auth.auth_buffers[3].length = sizeof (isp->sess_auth.auth_recv_binary_block); isp->sess_auth.auth_buffers[4].address = &(isp->sess_auth.auth_send_binary_block); isp->sess_auth.auth_buffers[4].length = sizeof (isp->sess_auth.auth_send_binary_block); } return (B_TRUE); } /* * iscsi_sess_reserve_itt - Used to reserve an ITT hash slot */ iscsi_status_t iscsi_sess_reserve_scsi_itt(iscsi_cmd_t *icmdp) { idm_task_t *itp; iscsi_conn_t *icp = icmdp->cmd_conn; itp = idm_task_alloc(icp->conn_ic); if (itp == NULL) return (ISCSI_STATUS_INTERNAL_ERROR); itp->idt_private = icmdp; icmdp->cmd_itp = itp; icmdp->cmd_itt = itp->idt_tt; return (ISCSI_STATUS_SUCCESS); } /* * iscsi_sess_release_scsi_itt - Used to release ITT hash slot */ void iscsi_sess_release_scsi_itt(iscsi_cmd_t *icmdp) { idm_task_free(icmdp->cmd_itp); } /* * iscsi_sess_reserve_itt - Used to reserve an ITT hash slot */ iscsi_status_t iscsi_sess_reserve_itt(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { /* If no more slots are open fail reservation */ if (isp->sess_cmd_table_count >= ISCSI_CMD_TABLE_SIZE) { return (ISCSI_STATUS_ITT_TABLE_FULL); } /* * Keep itt values out of the range used by IDM */ if (isp->sess_itt < IDM_TASKIDS_MAX) isp->sess_itt = IDM_TASKIDS_MAX; /* * Find the next available slot. Normally its the * slot pointed to by the session's sess_itt value. * If this is not true the table has become fragmented. * Fragmentation can occur during max loads and IOs * are completed out of order. Defragmentation will * occur when IO slows down and ITT slots are released. */ while (isp->sess_cmd_table[isp->sess_itt % ISCSI_CMD_TABLE_SIZE] != NULL) { isp->sess_itt++; } /* reserve slot and update counters */ icmdp->cmd_itt = isp->sess_itt; isp->sess_cmd_table[isp->sess_itt % ISCSI_CMD_TABLE_SIZE] = icmdp; isp->sess_cmd_table_count++; isp->sess_itt++; return (ISCSI_STATUS_SUCCESS); } /* * iscsi_sess_release_itt - Used to release ITT hash slot */ void iscsi_sess_release_itt(iscsi_sess_t *isp, iscsi_cmd_t *icmdp) { int hash_index = (icmdp->cmd_itt % ISCSI_CMD_TABLE_SIZE); ASSERT(isp->sess_cmd_table[hash_index] != NULL); /* release slot and update counters */ isp->sess_cmd_table[hash_index] = NULL; isp->sess_cmd_table_count--; } /* * iscsi_sess_redrive_io - Used to redrive IO on connections in * a full feature state. */ void iscsi_sess_redrive_io(iscsi_sess_t *isp) { iscsi_conn_t *icp; ASSERT(isp != NULL); icp = isp->sess_conn_list; while (icp != NULL) { if (ISCSI_CONN_STATE_FULL_FEATURE( icp->conn_state)) { (void) iscsi_thread_send_wakeup( icp->conn_tx_thread); } icp = icp->conn_next; } } /* * iscsi_sess_state_machine - * * 7.3.1 Session State Diagram for an Initiator * * Symbolic Names for States: * Q1: FREE - State on instantiation of after cleanup * Q3: LOGGED_IN - Waiting for all session events. * Q4: FAILED - Waiting for session recovery or session cont. * Q5: IN_FLUSH - A login parameter has changed. We are in the * process of flushing active, aborting, and * completed queues. Once flushed the iscsi_ic_thread() * will drop of drop connections (T14) and reconnect * to the target with new values. * Q6: FLUSHED - Active, Aborting and Completed Queues flushed. * Awaiting reconnect or failure. iscsi_tx/ic_threads * are still running and might be timing-out IOs. * State Q3/4 represent the Full Feature Phase operation of the session. * * The state diagram is as follows: * * ------ (N5/6/7 == NOOP) * / Q1 \ * +------------------------->\ /<-------------+ * | ---+--- | * | N5 |N1 | * | +------+ +-------------+ | | * | | V V | V | * | | ----+-- -----+ | * |N6|N5/7 / Q4 \ / Q3 \(N6 == NOOP) | * +--+-----\ /----+--->\ /-----+---------+ * | ------- /N1 -+---- | N3| * | (N7 == NOOP) / N7| ^ N1/3/5| | * | / | +-------+ | * | +-------+ / | | * | | V / v | * | | ------- -+---- | * |N6|N6 / Q6 \ N5 / Q5 \ | * +--+-----\ /<--------\ /-----+---------+ * ------- ------ | N3 * (N7 == NOOP) ^ N1/3/5| * +-------+ * * The state transition table is as follows: * * +------+------+----+--------+----+ * |Q1 |Q3 |Q4 |Q5 |Q6 | * -----+------+------+----+--------+----+ * Q1 |N5/6/7|N1 | - | | | * -----+------+------+----+--------+----+ * Q3 |N3 |N1/3/5|N5 |N7 | | * -----+------+------+----+--------+----+ * Q4 |N6 |N1 |N5/7| | | * -----+------+------+----+--------+----+ * Q5 |N3 | | |N1/3/5/7|N6 | * -----+------+------+----+--------+----+ * Q6 |N6 |N1 |N6/7| | | * -----+------+------+----+--------+----+ * * Event definitions: * * -N1: A connection logged in * -N3: A connection logged out * -N5: A connection failed * -N6: Session state timeout occurred, or a session * reinstatement cleared this session instance. This results in * the freeing of all associated resources and the session state * is discarded. * -N7: Login parameters for session have changed. * Re-negeotation required. * * Any caller to the state machine (and so as a state writer) must * enter the state zone before calling this function, and vice versa * any caller that doesn't change the state machine shouldn't enter * the zone, and should act as a reader for a better performance. * * The handler of state transition shouldn't try to enter the state * zone in the same thread or dead lock will occur. */ void iscsi_sess_state_machine(iscsi_sess_t *isp, iscsi_sess_event_t event, uint32_t event_count) { ASSERT(isp != NULL); ASSERT(rw_read_locked(&isp->sess_state_rwlock) == 0); DTRACE_PROBE3(event, iscsi_sess_t *, isp, char *, iscsi_sess_state_str(isp->sess_state), char *, iscsi_sess_event_str(event)); /* Audit event */ idm_sm_audit_event(&isp->sess_state_audit, SAS_ISCSI_SESS, isp->sess_state, event, 0); isp->sess_prev_state = isp->sess_state; isp->sess_state_lbolt = ddi_get_lbolt(); ISCSI_SESS_LOG(CE_NOTE, "DEBUG: sess_state: isp: %p state: %d event: %d event count: %d", (void *)isp, isp->sess_state, event, event_count); switch (isp->sess_state) { case ISCSI_SESS_STATE_FREE: iscsi_sess_state_free(isp, event, event_count); break; case ISCSI_SESS_STATE_LOGGED_IN: iscsi_sess_state_logged_in(isp, event, event_count); break; case ISCSI_SESS_STATE_FAILED: iscsi_sess_state_failed(isp, event, event_count); break; case ISCSI_SESS_STATE_IN_FLUSH: iscsi_sess_state_in_flush(isp, event, event_count); break; case ISCSI_SESS_STATE_FLUSHED: iscsi_sess_state_flushed(isp, event, event_count); break; default: ASSERT(FALSE); } /* Audit state change */ if (isp->sess_prev_state != isp->sess_state) { idm_sm_audit_state_change(&isp->sess_state_audit, SAS_ISCSI_SESS, isp->sess_prev_state, isp->sess_state); } } /* * iscsi_sess_state_str - * */ char * iscsi_sess_state_str(iscsi_sess_state_t state) { switch (state) { case ISCSI_SESS_STATE_FREE: return ("free"); case ISCSI_SESS_STATE_LOGGED_IN: return ("logged_in"); case ISCSI_SESS_STATE_FAILED: return ("failed"); case ISCSI_SESS_STATE_IN_FLUSH: return ("in_flush"); case ISCSI_SESS_STATE_FLUSHED: return ("flushed"); default: return ("unknown"); } } /* * +--------------------------------------------------------------------+ * | Internal Session Interfaces | * +--------------------------------------------------------------------+ */ /* * iscsi_sess_state_free - * */ static void iscsi_sess_state_free(iscsi_sess_t *isp, iscsi_sess_event_t event, uint32_t event_count) { iscsi_hba_t *ihp; iscsi_enum_result_t enum_result; ASSERT(isp != NULL); ihp = isp->sess_hba; ASSERT(ihp != NULL); ASSERT(isp->sess_state == ISCSI_SESS_STATE_FREE); /* switch on event change */ switch (event) { /* * -N1: A connection logged in */ case ISCSI_SESS_EVENT_N1: isp->sess_state = ISCSI_SESS_STATE_LOGGED_IN; rw_downgrade(&isp->sess_state_rwlock); if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) { cmn_err(CE_NOTE, "!iscsi session(%u) %s online\n", isp->sess_oid, isp->sess_name); enum_result = iscsi_sess_enum_request(isp, B_TRUE, event_count); if (enum_result == ISCSI_SESS_ENUM_SUBMITTED) { enum_result = iscsi_sess_enum_query(isp); } if (enum_result != ISCSI_SESS_ENUM_COMPLETE) { iscsi_sess_enum_warn(isp, enum_result); } } break; /* * -N5: A connection failed */ case ISCSI_SESS_EVENT_N5: /* NOOP - not connected */ break; /* * -N6: Session state timeout occurred, or a session * reinstatement cleared this session instance. This results in * the freeing of all associated resources and the session state * is discarded. */ case ISCSI_SESS_EVENT_N6: /* FALLTHRU */ /* * -N7: Login parameters for session have changed. * Re-negeotation required. */ case ISCSI_SESS_EVENT_N7: /* NOOP - not connected */ break; /* All other events are invalid for this state */ default: ASSERT(FALSE); } } /* * iscsi_sess_logged_in - * */ static void iscsi_sess_state_logged_in(iscsi_sess_t *isp, iscsi_sess_event_t event, uint32_t event_count) { iscsi_enum_result_t enum_result; ASSERT(isp != NULL); ASSERT(isp->sess_state == ISCSI_SESS_STATE_LOGGED_IN); /* switch on event change */ switch (event) { /* * -N1: At least one transport connection reached the * LOGGED_IN state */ case ISCSI_SESS_EVENT_N1: /* * A different connection already logged in. If the * session is NORMAL, just re-enumerate the session. */ if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) { rw_downgrade(&isp->sess_state_rwlock); enum_result = iscsi_sess_enum_request(isp, B_TRUE, event_count); if (enum_result == ISCSI_SESS_ENUM_SUBMITTED) { enum_result = iscsi_sess_enum_query(isp); } if (enum_result != ISCSI_SESS_ENUM_COMPLETE) { iscsi_sess_enum_warn(isp, enum_result); } } break; /* * -N3: A connection logged out. */ case ISCSI_SESS_EVENT_N3: /* FALLTHRU */ /* * -N5: A connection failed */ case ISCSI_SESS_EVENT_N5: /* * MC/S: If this is the last connection to * fail then move the the failed state. */ if (event == ISCSI_SESS_EVENT_N3) { isp->sess_state = ISCSI_SESS_STATE_FREE; } else { isp->sess_state = ISCSI_SESS_STATE_FAILED; } rw_downgrade(&isp->sess_state_rwlock); /* no longer connected reset nego tpgt */ isp->sess_tpgt_nego = ISCSI_DEFAULT_TPGT; iscsi_sess_flush(isp); if (event == ISCSI_SESS_EVENT_N3) { if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) { cmn_err(CE_NOTE, "!iscsi session(%u) %s offline\n", isp->sess_oid, isp->sess_name); } /* * During the process of offlining the LUNs * our ic thread might be calling back into * the driver via a target driver failure * path to do a reset or something * we need to release the sess_state_mutex * while we are killing these threads so * they don't get deadlocked. */ iscsi_sess_offline_luns(isp); } mutex_enter(&isp->sess_reset_mutex); isp->sess_reset_in_progress = B_FALSE; mutex_exit(&isp->sess_reset_mutex); /* update busy luns if needed */ iscsi_sess_update_busy_luns(isp, B_TRUE); break; /* * -N6: Session state timeout occurred, or a session * reinstatement cleared this session instance. This results in * the freeing of all associated resources and the session state * is discarded. */ case ISCSI_SESS_EVENT_N6: /* NOOP - Not last connection */ break; /* * -N7: Login parameters for session have changed. * Re-negeotation required. */ case ISCSI_SESS_EVENT_N7: isp->sess_state = ISCSI_SESS_STATE_IN_FLUSH; break; /* All other events are invalid for this state */ default: ASSERT(FALSE); } } /* * iscsi_sess_state_failed - * */ static void iscsi_sess_state_failed(iscsi_sess_t *isp, iscsi_sess_event_t event, uint32_t event_count) { iscsi_hba_t *ihp; iscsi_enum_result_t enum_result; ASSERT(isp != NULL); ihp = isp->sess_hba; ASSERT(ihp != NULL); ASSERT(isp->sess_state == ISCSI_SESS_STATE_FAILED); /* switch on event change */ switch (event) { /* -N1: A session continuation attempt succeeded */ case ISCSI_SESS_EVENT_N1: isp->sess_state = ISCSI_SESS_STATE_LOGGED_IN; rw_downgrade(&isp->sess_state_rwlock); if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) { enum_result = iscsi_sess_enum_request(isp, B_TRUE, event_count); if (enum_result == ISCSI_SESS_ENUM_SUBMITTED) { enum_result = iscsi_sess_enum_query(isp); } if (enum_result != ISCSI_SESS_ENUM_COMPLETE) { iscsi_sess_enum_warn(isp, enum_result); } } break; /* * -N5: A connection failed */ case ISCSI_SESS_EVENT_N5: /* NOOP - not connected */ break; /* * -N6: Session state timeout occurred, or a session * reinstatement cleared this session instance. This results in * the freeing of all associated resources and the session state * is discarded. */ case ISCSI_SESS_EVENT_N6: isp->sess_state = ISCSI_SESS_STATE_FREE; if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) { cmn_err(CE_NOTE, "!iscsi session(%u) %s offline\n", isp->sess_oid, isp->sess_name); } rw_downgrade(&isp->sess_state_rwlock); iscsi_sess_offline_luns(isp); break; /* * -N7: Login parameters for session have changed. * Re-negeotation required. */ case ISCSI_SESS_EVENT_N7: /* NOOP - not connected */ break; /* All other events are invalid for this state */ default: ASSERT(FALSE); } } /* * iscsi_sess_state_in_flush - * */ /* ARGSUSED */ static void iscsi_sess_state_in_flush(iscsi_sess_t *isp, iscsi_sess_event_t event, uint32_t event_count) { ASSERT(isp != NULL); ASSERT(isp->sess_state == ISCSI_SESS_STATE_IN_FLUSH); /* switch on event change */ switch (event) { /* -N1: A session continuation attempt succeeded */ case ISCSI_SESS_EVENT_N1: /* NOOP - connections already online */ break; /* * -N3: A connection logged out. */ case ISCSI_SESS_EVENT_N3: /* FALLTHRU */ /* * -N5: A connection failed */ case ISCSI_SESS_EVENT_N5: /* * MC/S: If this is the last connection to * fail then move the the failed state. */ if (event == ISCSI_SESS_EVENT_N3) { isp->sess_state = ISCSI_SESS_STATE_FREE; } else { isp->sess_state = ISCSI_SESS_STATE_FLUSHED; } rw_downgrade(&isp->sess_state_rwlock); /* no longer connected reset nego tpgt */ isp->sess_tpgt_nego = ISCSI_DEFAULT_TPGT; iscsi_sess_flush(isp); if (event == ISCSI_SESS_EVENT_N3) { if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) { cmn_err(CE_NOTE, "!iscsi session(%u) %s offline\n", isp->sess_oid, isp->sess_name); } /* * During the process of offlining the LUNs * our ic thread might be calling back into * the driver via a target driver failure * path to do a reset or something * we need to release the sess_state_mutex * while we are killing these threads so * they don't get deadlocked. */ iscsi_sess_offline_luns(isp); } mutex_enter(&isp->sess_reset_mutex); isp->sess_reset_in_progress = B_FALSE; mutex_exit(&isp->sess_reset_mutex); /* update busy luns if needed */ iscsi_sess_update_busy_luns(isp, B_TRUE); break; /* * -N6: Session state timeout occurred, or a session * reinstatement cleared this session instance. This results in * the freeing of all associated resources and the session state * is discarded. */ case ISCSI_SESS_EVENT_N6: /* NOOP - Not last connection */ break; /* * -N7: Login parameters for session have changed. * Re-negeotation required. */ case ISCSI_SESS_EVENT_N7: /* NOOP - Already attempting to update */ break; /* All other events are invalid for this state */ default: ASSERT(FALSE); } } /* * iscsi_sess_state_flushed - * */ static void iscsi_sess_state_flushed(iscsi_sess_t *isp, iscsi_sess_event_t event, uint32_t event_count) { iscsi_hba_t *ihp; iscsi_enum_result_t enum_result; ASSERT(isp != NULL); ASSERT(isp->sess_state == ISCSI_SESS_STATE_FLUSHED); ihp = isp->sess_hba; ASSERT(ihp != NULL); /* switch on event change */ switch (event) { /* -N1: A session continuation attempt succeeded */ case ISCSI_SESS_EVENT_N1: isp->sess_state = ISCSI_SESS_STATE_LOGGED_IN; rw_downgrade(&isp->sess_state_rwlock); if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) { enum_result = iscsi_sess_enum_request(isp, B_TRUE, event_count); if (enum_result == ISCSI_SESS_ENUM_SUBMITTED) { enum_result = iscsi_sess_enum_query(isp); } if (enum_result != ISCSI_SESS_ENUM_COMPLETE) { iscsi_sess_enum_warn(isp, enum_result); } } break; /* * -N6: Session state timeout occurred, or a session * reinstatement cleared this session instance. This results in * the freeing of all associated resources and the session state * is discarded. */ case ISCSI_SESS_EVENT_N6: isp->sess_state = ISCSI_SESS_STATE_FREE; rw_downgrade(&isp->sess_state_rwlock); if (isp->sess_type == ISCSI_SESS_TYPE_NORMAL) { cmn_err(CE_NOTE, "!iscsi session(%u) %s offline\n", isp->sess_oid, isp->sess_name); } iscsi_sess_offline_luns(isp); break; /* * -N7: Login parameters for session have changed. * Re-negeotation required. */ case ISCSI_SESS_EVENT_N7: /* NOOP - not connected */ break; /* All other events are invalid for this state */ default: ASSERT(FALSE); } } /* * iscsi_sess_event_str - * */ static char * iscsi_sess_event_str(iscsi_sess_event_t event) { switch (event) { case ISCSI_SESS_EVENT_N1: return ("N1"); case ISCSI_SESS_EVENT_N3: return ("N3"); case ISCSI_SESS_EVENT_N5: return ("N5"); case ISCSI_SESS_EVENT_N6: return ("N6"); case ISCSI_SESS_EVENT_N7: return ("N7"); default: return ("unknown"); } } /* * iscsi_sess_thread_create - * */ static iscsi_status_t iscsi_sess_threads_create(iscsi_sess_t *isp) { iscsi_hba_t *ihp; char th_name[ISCSI_TH_MAX_NAME_LEN]; ASSERT(isp != NULL); ihp = isp->sess_hba; ASSERT(ihp != NULL); /* Completion thread creation. */ if (snprintf(th_name, sizeof (th_name) - 1, ISCSI_SESS_IOTH_NAME_FORMAT, ihp->hba_oid, isp->sess_oid) >= sizeof (th_name)) { return (ISCSI_STATUS_INTERNAL_ERROR); } isp->sess_ic_thread = iscsi_thread_create(ihp->hba_dip, th_name, iscsi_ic_thread, isp); if (isp->sess_ic_thread == NULL) { return (ISCSI_STATUS_INTERNAL_ERROR); } (void) iscsi_thread_start(isp->sess_ic_thread); return (ISCSI_STATUS_SUCCESS); } /* * iscsi_sess_enumeration - This function is used to drive the enumeration * of LUs on a session. It will first prepare the target by sending test * unit ready commands, then it will issue a report luns. If the report * luns is successful then it will process all the luns in the report. * If report luns is not successful we will do a stepping enumeration * of luns until no more luns are found. */ static void iscsi_sess_enumeration(void *arg) { iscsi_task_t *itp = (iscsi_task_t *)arg; iscsi_sess_t *isp; iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_enum_result_t enum_result = ISCSI_SESS_ENUM_COMPLETE; uint32_t event_count = itp->t_event_count; ASSERT(itp != NULL); isp = (iscsi_sess_t *)itp->t_arg; ASSERT(isp != NULL); /* * Send initial TEST_UNIT_READY to target. If it fails this we * stop our enumeration as the target is not responding properly. */ rval = iscsi_sess_testunitready(isp, event_count); if (ISCSI_SUCCESS(rval)) { /* * Now we know the target is ready start our enumeration with * REPORT LUNs, If this fails we will have to fall back to * stepping */ rval = iscsi_sess_reportluns(isp, event_count); if (!ISCSI_SUCCESS(rval)) { /* * report luns failed so lets just check for LUN 0. * This will match fcp's enumeration support and * avoid issues with older devices like the A5K that * respond poorly. */ if (isp->sess_lun_list == NULL) { iscsi_sess_inquiry(isp, 0, 0, event_count, NULL); } } } else { enum_result = ISCSI_SESS_ENUM_TUR_FAIL; } kmem_free(itp, sizeof (iscsi_task_t)); mutex_enter(&isp->sess_enum_lock); if (isp->sess_enum_result_count != 0) { isp->sess_enum_status = ISCSI_SESS_ENUM_DONE; } else { isp->sess_enum_status = ISCSI_SESS_ENUM_FREE; } isp->sess_enum_result = enum_result; cv_broadcast(&isp->sess_enum_cv); mutex_exit(&isp->sess_enum_lock); } /* * iscsi_sess_testunitready - This is used during enumeration to * ensure an array is ready to be enumerated. */ static iscsi_status_t iscsi_sess_testunitready(iscsi_sess_t *isp, uint32_t event_count) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; int retries = 0; struct uscsi_cmd ucmd; char cdb[CDB_GROUP0]; ASSERT(isp != NULL); /* loop until successful sending test unit ready or retries out */ while ((retries++ < 3) && (isp->sess_state_event_count == event_count)) { /* cdb is all zeros */ bzero(&cdb[0], CDB_GROUP0); /* setup uscsi cmd */ bzero(&ucmd, sizeof (struct uscsi_cmd)); ucmd.uscsi_timeout = iscsi_sess_enum_timeout; ucmd.uscsi_cdb = &cdb[0]; ucmd.uscsi_cdblen = CDB_GROUP0; /* send test unit ready to lun zero on this session */ rval = iscsi_handle_passthru(isp, 0, &ucmd); /* * If passthru was successful then we were able to * communicate with the target, continue enumeration. */ if (ISCSI_SUCCESS(rval)) { break; } } return (rval); } #define SCSI_REPORTLUNS_ADDRESS_SIZE 8 #define SCSI_REPORTLUNS_ADDRESS_MASK 0xC0 #define SCSI_REPORTLUNS_ADDRESS_PERIPHERAL 0x00 #define SCSI_REPORTLUNS_ADDRESS_FLAT_SPACE 0x40 #define SCSI_REPORTLUNS_ADDRESS_LOGICAL_UNIT 0x80 #define SCSI_REPORTLUNS_ADDRESS_EXTENDED_UNIT 0xC0 #define SCSI_REPORTLUNS_ADDRESS_LOGICAL_UNIT_2B 0x00 #define SCSI_REPORTLUNS_ADDRESS_LOGICAL_UNIT_4B 0x01 #define SCSI_REPORTLUNS_ADDRESS_LOGICAL_UNIT_6B 0x10 #define SCSI_REPORTLUNS_ADDRESS_LOGICAL_UNIT_8B 0x20 #define SCSI_REPORTLUNS_ADDRESS_LOGICAL_UNIT_SIZE 0x30 /* * iscsi_sess_reportluns - This is used during enumeration to * ensure an array is ready to be enumerated. */ static iscsi_status_t iscsi_sess_reportluns(iscsi_sess_t *isp, uint32_t event_count) { iscsi_status_t rval = ISCSI_STATUS_SUCCESS; iscsi_hba_t *ihp; struct uscsi_cmd ucmd; unsigned char cdb[CDB_GROUP5]; unsigned char *buf = NULL; int buf_len = sizeof (struct scsi_inquiry); uint32_t lun_list_length = 0; uint16_t lun_num = 0; uint8_t lun_addr_type = 0; uint32_t lun_count = 0; uint32_t lun_start = 0; uint32_t lun_total = 0; int retries = 0; iscsi_lun_t *ilp_next; iscsi_lun_t *ilp = NULL; replun_data_t *saved_replun_ptr = NULL; ASSERT(isp != NULL); ihp = isp->sess_hba; ASSERT(ihp != NULL); /* * Attempt to send report luns until we successfully * get all the data or the retries run out. */ while ((retries++ < 3) && (isp->sess_state_event_count == event_count)) { /* * Allocate our buffer based on current buf_len. * buf_len may change after we received a response * from the target. */ if (buf == NULL) { buf = kmem_zalloc(buf_len, KM_SLEEP); } /* setup cdb */ bzero(&cdb, CDB_GROUP5); cdb[0] = SCMD_REPORT_LUNS; cdb[6] = (buf_len & 0xff000000) >> 24; cdb[7] = (buf_len & 0x00ff0000) >> 16; cdb[8] = (buf_len & 0x0000ff00) >> 8; cdb[9] = (buf_len & 0x000000ff); /* setup uscsi cmd */ bzero(&ucmd, sizeof (struct uscsi_cmd)); ucmd.uscsi_flags = USCSI_READ; ucmd.uscsi_timeout = iscsi_sess_enum_timeout; ucmd.uscsi_cdb = (char *)&cdb[0]; ucmd.uscsi_cdblen = CDB_GROUP5; ucmd.uscsi_bufaddr = (char *)buf; ucmd.uscsi_buflen = buf_len; /* send uscsi cmd to lun 0 on session */ rval = iscsi_handle_passthru(isp, 0, &ucmd); /* If passthru successful but not scsi status update istatus */ if (ISCSI_SUCCESS(rval) && (ucmd.uscsi_status != STATUS_GOOD)) { rval = ISCSI_STATUS_USCSI_FAILED; } /* If successful, check if we have all the data */ if (ISCSI_SUCCESS(rval)) { /* total data - header (SCSI_REPORTLUNS_ADDRESS_SIZE) */ lun_list_length = htonl(*(uint32_t *)buf); if (buf_len >= lun_list_length + SCSI_REPORTLUNS_ADDRESS_SIZE) { /* we have all the data, were done */ break; } /* * We don't have all the data. free up the * memory for the next pass and update the * buf_len */ kmem_free(buf, buf_len); buf = NULL; buf_len = lun_list_length + SCSI_REPORTLUNS_ADDRESS_SIZE; } else { retries++; } } if (isp->sess_state_event_count != event_count) { if (buf != NULL) { kmem_free(buf, buf_len); buf = NULL; } return (rval); } /* If not successful go no further */ if (!ISCSI_SUCCESS(rval)) { kmem_free(buf, buf_len); return (rval); } /* * find out the number of luns returned by the SCSI ReportLun call * and allocate buffer space */ lun_total = lun_list_length / SCSI_REPORTLUNS_ADDRESS_SIZE; saved_replun_ptr = kmem_zalloc(lun_total * sizeof (replun_data_t), KM_SLEEP); /* * walk the isp->sess_lun_list * for each lun in this list * look to see if this lun is in the SCSI ReportLun list we * just retrieved * if it is in the SCSI ReportLun list and it is already ONLINE or * if it is in the SCSI ReportLun list and it is OFFLINE or * if it isn't in the SCSI ReportLunlist or then * issue the iscsi_sess_inquiry() to handle * * as we walk the SCSI ReportLun list, we save this lun information * into the buffer we just allocated. This will save us from * having to figure out this information later */ lun_start = 0; rw_enter(&isp->sess_lun_list_rwlock, RW_WRITER); for (ilp = isp->sess_lun_list; ilp; ilp = ilp_next) { if (isp->sess_state_event_count != event_count) break; ilp_next = ilp->lun_next; for (lun_count = lun_start; lun_count < lun_total; lun_count++) { /* * if the first lun in saved_replun_ptr buffer has * already been found we can move on and do not * have to check this lun in the future */ if (lun_count == lun_start && saved_replun_ptr[lun_start].lun_found) { lun_start++; continue; } /* * check to see if the lun we are looking for is in the * saved_replun_ptr buffer * if it is, process the lun * if it isn't, then we must go to SCSI * Report Lun buffer * we retrieved to get lun info */ if ((saved_replun_ptr[lun_count].lun_valid == B_TRUE) && (saved_replun_ptr[lun_count].lun_num == ilp->lun_num)) { /* * the lun we are looking for is found, * give it to iscsi_sess_inquiry() */ rw_exit(&isp->sess_lun_list_rwlock); iscsi_sess_inquiry(isp, ilp->lun_num, saved_replun_ptr[lun_count].lun_addr_type, event_count, ilp); rw_enter(&isp->sess_lun_list_rwlock, RW_WRITER); saved_replun_ptr[lun_count].lun_found = B_TRUE; break; } else { /* * lun information is not found in the * saved_replun buffer, retrieve lun * information from the SCSI Report Lun buffer * and store this information in the * saved_replun buffer */ if (retrieve_lundata(lun_count, buf, isp, &lun_num, &lun_addr_type) != ISCSI_STATUS_SUCCESS) { continue; } saved_replun_ptr[lun_count].lun_valid = B_TRUE; saved_replun_ptr[lun_count].lun_num = lun_num; saved_replun_ptr[lun_count].lun_addr_type = lun_addr_type; if (ilp->lun_num == lun_num) { /* * lun is found in the SCSI Report Lun * buffer, give it to inquiry */ rw_exit(&isp->sess_lun_list_rwlock); iscsi_sess_inquiry(isp, lun_num, lun_addr_type, event_count, ilp); rw_enter(&isp->sess_lun_list_rwlock, RW_WRITER); saved_replun_ptr[lun_count].lun_found = B_TRUE; break; } } } if (lun_count == lun_total) { /* * this lun we found in the sess->lun_list does * not exist anymore, need to offline this lun */ DTRACE_PROBE2( sess_reportluns_lun_no_longer_exists, int, ilp->lun_num, int, ilp->lun_state); (void) iscsi_lun_destroy(ihp, ilp); } } rw_exit(&isp->sess_lun_list_rwlock); /* * look for new luns that we found in the SCSI Report Lun buffer that * we did not have in the sess->lun_list and add them into the list */ for (lun_count = lun_start; lun_count < lun_total; lun_count++) { if (saved_replun_ptr[lun_count].lun_valid == B_FALSE) { /* * lun information is not in the * saved_replun buffer, retrieve * it from the SCSI Report Lun buffer */ if (retrieve_lundata(lun_count, buf, isp, &lun_num, &lun_addr_type) != ISCSI_STATUS_SUCCESS) { continue; } } else { /* * lun information is in the saved_replun buffer * if this lun has been found already, * then we can move on */ if (saved_replun_ptr[lun_count].lun_found == B_TRUE) { continue; } lun_num = saved_replun_ptr[lun_count].lun_num; lun_addr_type = saved_replun_ptr[lun_count].lun_addr_type; } /* New luns found should not conflict with existing luns */ rw_enter(&isp->sess_lun_list_rwlock, RW_READER); for (ilp = isp->sess_lun_list; ilp; ilp = ilp->lun_next) { if (ilp->lun_num == lun_num) { break; } } rw_exit(&isp->sess_lun_list_rwlock); if (ilp == NULL) { /* new lun found, add this lun */ iscsi_sess_inquiry(isp, lun_num, lun_addr_type, event_count, NULL); } else { cmn_err(CE_NOTE, "!Duplicate Lun Number(%d) recieved from " "Target(%s)", lun_num, isp->sess_name); } } if (buf != NULL) { kmem_free(buf, buf_len); } kmem_free(saved_replun_ptr, lun_total * sizeof (replun_data_t)); return (rval); } #define ISCSI_MAX_INQUIRY_BUF_SIZE 0xFF #define ISCSI_MAX_INQUIRY_RETRIES 3 /* * iscsi_sess_inquiry - Final processing of a LUN before we create a tgt * mapping, if necessary the old lun will be deleted. * * We need to collect the stardard inquiry page and the * vendor identification page for this LUN. If both of these are * successful and the identification page contains a NAA or EUI type * we will continue. Otherwise we fail the creation of a tgt for * this LUN. * * Keep the old lun unchanged if it is online and following things are * match, lun_addr_type, lun_type, and lun_guid. * * Online the old lun if it is offline/invalid and those three things * are match. * * Online a new lun if the old lun is offline and any of those three things * is not match, and needs to destroy the old first. * * Destroy the old lun and online the new lun if the old is online/invalid * and any of those three things is not match, and then online the new lun */ static void iscsi_sess_inquiry(iscsi_sess_t *isp, uint16_t lun_num, uint8_t lun_addr_type, uint32_t event_count, iscsi_lun_t *ilp) { iscsi_status_t rval; struct uscsi_cmd ucmd; uchar_t cdb[CDB_GROUP0]; uchar_t *inq; size_t inq_len; uchar_t *inq83; size_t inq83_len; int retries; ddi_devid_t devid; char *guid = NULL; iscsi_hba_t *ihp; iscsi_status_t status = ISCSI_STATUS_SUCCESS; boolean_t inq_ready = B_FALSE; boolean_t inq83_ready = B_FALSE; boolean_t nochange = B_FALSE; uchar_t lun_type; ASSERT(isp != NULL); ihp = isp->sess_hba; ASSERT(ihp != NULL); inq = kmem_zalloc(ISCSI_MAX_INQUIRY_BUF_SIZE, KM_SLEEP); inq83 = kmem_zalloc(ISCSI_MAX_INQUIRY_BUF_SIZE, KM_SLEEP); if (ilp == NULL) { /* easy case, just to create the new lun */ goto sess_inq; } if (ilp->lun_addr_type != lun_addr_type) { goto offline_old; } goto sess_inq; offline_old: if (isp->sess_state_event_count != event_count) { goto inq_done; } status = iscsi_lun_destroy(ihp, ilp); if (status != ISCSI_STATUS_SUCCESS) { /* have to abort the process */ cmn_err(CE_WARN, "iscsi session(%u) is unable to offline" " obsolete logical unit %d", isp->sess_oid, lun_num); goto inq_done; } ilp = NULL; sess_inq: if (inq_ready == B_TRUE) { goto sess_inq83; } /* * STANDARD INQUIRY - We need the standard inquiry information * to feed into the scsi_hba_nodename_compatible_get function. * This function is used to detemine which driver will bind * on top of us, via the compatible id. */ bzero(&cdb, CDB_GROUP0); cdb[0] = SCMD_INQUIRY; cdb[4] = ISCSI_MAX_INQUIRY_BUF_SIZE; bzero(&ucmd, sizeof (struct uscsi_cmd)); ucmd.uscsi_flags = USCSI_READ; ucmd.uscsi_timeout = iscsi_sess_enum_timeout; ucmd.uscsi_cdb = (char *)&cdb[0]; ucmd.uscsi_cdblen = CDB_GROUP0; ucmd.uscsi_bufaddr = (char *)inq; ucmd.uscsi_buflen = ISCSI_MAX_INQUIRY_BUF_SIZE; /* Attempt to get inquiry information until successful or retries */ retries = 0; while ((retries++ < ISCSI_MAX_INQUIRY_RETRIES) && (isp->sess_state_event_count == event_count)) { /* issue passthru */ rval = iscsi_handle_passthru(isp, lun_num, &ucmd); /* If we were successful but scsi stat failed update istatus */ if (ISCSI_SUCCESS(rval) && (ucmd.uscsi_status != STATUS_GOOD)) { rval = ISCSI_STATUS_USCSI_FAILED; } /* If successful break */ if (ISCSI_SUCCESS(rval)) { inq_len = ISCSI_MAX_INQUIRY_BUF_SIZE - ucmd.uscsi_resid; break; } /* loop until we are successful or retries run out */ } /* If failed don't continue */ if (!ISCSI_SUCCESS(rval)) { cmn_err(CE_NOTE, "iscsi session(%u) unable to enumerate " "logical unit - inquiry failed lun %d", isp->sess_oid, lun_num); goto inq_done; } inq_ready = B_TRUE; sess_inq83: /* * T-10 SPC Section 6.4.2. Standard INQUIRY Peripheral * qualifier of 000b is the only type we should attempt * to plumb under the IO stack. */ if ((inq[0] & SCSI_INQUIRY_PQUAL_MASK) != 0x00) { /* shouldn't enumerate, destroy the old one if exists */ if (ilp != NULL) { goto offline_old; } goto inq_done; } /* * If lun type has changed */ lun_type = ((struct scsi_inquiry *)inq)->inq_dtype & DTYPE_MASK; if ((ilp != NULL) && (ilp->lun_type != lun_type)) { goto offline_old; } if (inq83_ready == B_TRUE) { goto guid_ready; } /* * VENDOR IDENTIFICATION INQUIRY - This will be used to identify * a unique lunId. This Id is passed to the mdi alloc calls so * we can properly plumb into scsi_vhci/mpxio. */ bzero(&cdb, CDB_GROUP0); cdb[0] = SCMD_INQUIRY; cdb[1] = 0x01; /* EVP bit */ cdb[2] = 0x83; cdb[4] = ISCSI_MAX_INQUIRY_BUF_SIZE; ucmd.uscsi_flags = USCSI_READ; ucmd.uscsi_timeout = iscsi_sess_enum_timeout; ucmd.uscsi_cdb = (char *)&cdb[0]; ucmd.uscsi_cdblen = CDB_GROUP0; ucmd.uscsi_bufaddr = (char *)inq83; ucmd.uscsi_buflen = ISCSI_MAX_INQUIRY_BUF_SIZE; /* Attempt to get inquiry information until successful or retries */ retries = 0; while ((retries++ < ISCSI_MAX_INQUIRY_RETRIES) && (isp->sess_state_event_count == event_count)) { /* issue passthru command */ rval = iscsi_handle_passthru(isp, lun_num, &ucmd); /* If we were successful but scsi stat failed update istatus */ if (ISCSI_SUCCESS(rval) && (ucmd.uscsi_status != STATUS_GOOD)) { rval = ISCSI_STATUS_USCSI_FAILED; } /* Break if successful */ if (ISCSI_SUCCESS(rval)) { inq83_len = ISCSI_MAX_INQUIRY_BUF_SIZE - ucmd.uscsi_resid; break; } } /* * If we were successful collecting page 83 data attempt * to generate a GUID. If no GUID can be generated then * the logical unit will skip attempt to plumb under * scsi_vhci/mpxio. */ if (ISCSI_SUCCESS(rval)) { /* create DEVID from inquiry data */ if (ddi_devid_scsi_encode( DEVID_SCSI_ENCODE_VERSION_LATEST, NULL, inq, inq_len, NULL, 0, inq83, inq83_len, &devid) == DDI_SUCCESS) { /* extract GUID from DEVID */ guid = ddi_devid_to_guid(devid); /* devid no longer needed */ ddi_devid_free(devid); } } inq83_ready = B_TRUE; guid_ready: if (ilp != NULL) { if ((guid == NULL) && (ilp->lun_guid == NULL)) { nochange = B_TRUE; } if ((guid != NULL) && (ilp->lun_guid != NULL) && ((strlen(guid) + 1) == ilp->lun_guid_size) && (bcmp(guid, ilp->lun_guid, ilp->lun_guid_size) == 0)) { nochange = B_TRUE; } if (nochange != B_TRUE) { goto offline_old; } if (ilp->lun_state & (ISCSI_LUN_STATE_OFFLINE | ISCSI_LUN_STATE_INVALID)) { if (isp->sess_state_event_count == event_count) { (void) iscsi_lun_online(ihp, ilp); } } } else { if (isp->sess_state_event_count == event_count) { (void) iscsi_lun_create(isp, lun_num, lun_addr_type, (struct scsi_inquiry *)inq, guid); } } inq_done: if (guid != NULL) { /* guid is no longer needed */ ddi_devid_free_guid(guid); } /* free up memory now that we are done */ kmem_free(inq, ISCSI_MAX_INQUIRY_BUF_SIZE); kmem_free(inq83, ISCSI_MAX_INQUIRY_BUF_SIZE); } static iscsi_status_t retrieve_lundata(uint32_t lun_count, unsigned char *buf, iscsi_sess_t *isp, uint16_t *lun_num, uint8_t *lun_addr_type) { uint32_t lun_idx = 0; ASSERT(lun_num != NULL); ASSERT(lun_addr_type != NULL); lun_idx = (lun_count + 1) * SCSI_REPORTLUNS_ADDRESS_SIZE; /* determine report luns addressing type */ switch (buf[lun_idx] & SCSI_REPORTLUNS_ADDRESS_MASK) { /* * Vendors in the field have been found to be concatenating * bus/target/lun to equal the complete lun value instead * of switching to flat space addressing */ /* 00b - peripheral device addressing method */ case SCSI_REPORTLUNS_ADDRESS_PERIPHERAL: /* FALLTHRU */ /* 10b - logical unit addressing method */ case SCSI_REPORTLUNS_ADDRESS_LOGICAL_UNIT: /* FALLTHRU */ /* 01b - flat space addressing method */ case SCSI_REPORTLUNS_ADDRESS_FLAT_SPACE: /* byte0 bit0-5=msb lun byte1 bit0-7=lsb lun */ *lun_addr_type = (buf[lun_idx] & SCSI_REPORTLUNS_ADDRESS_MASK) >> 6; *lun_num = (buf[lun_idx] & 0x3F) << 8; *lun_num |= buf[lun_idx + 1]; return (ISCSI_STATUS_SUCCESS); default: /* protocol error */ cmn_err(CE_NOTE, "iscsi session(%u) unable " "to enumerate logical units - report " "luns returned an unsupported format", isp->sess_oid); break; } return (ISCSI_STATUS_INTERNAL_ERROR); } /* * iscsi_sess_flush - flushes remaining pending io on the session */ static void iscsi_sess_flush(iscsi_sess_t *isp) { iscsi_cmd_t *icmdp; ASSERT(isp != NULL); ASSERT(isp->sess_state != ISCSI_SESS_STATE_LOGGED_IN); /* * Flush out any remaining commands in the pending * queue. */ mutex_enter(&isp->sess_queue_pending.mutex); icmdp = isp->sess_queue_pending.head; while (icmdp != NULL) { if (isp->sess_state == ISCSI_SESS_STATE_FAILED) { 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 = isp->sess_queue_pending.head; } mutex_exit(&isp->sess_queue_pending.mutex); } /* * iscsi_sess_offline_luns - offline all this sessions luns */ static void iscsi_sess_offline_luns(iscsi_sess_t *isp) { iscsi_lun_t *ilp; iscsi_hba_t *ihp; ASSERT(isp != NULL); ASSERT(isp->sess_state != ISCSI_SESS_STATE_LOGGED_IN); ihp = isp->sess_hba; ASSERT(ihp != NULL); rw_enter(&isp->sess_lun_list_rwlock, RW_READER); ilp = isp->sess_lun_list; while (ilp != NULL) { (void) iscsi_lun_offline(ihp, ilp, B_FALSE); ilp = ilp->lun_next; } rw_exit(&isp->sess_lun_list_rwlock); } /* * iscsi_sess_get_by_target - return the session structure for based on a * passed in target oid and hba instance. NOTE: There may be * multiple sessions associated with any given target. In this case, * we will return the first matching session. This function * is intended to be used in retrieving target info that is constant * across sessions (target name, alias, etc.). */ int iscsi_sess_get_by_target(uint32_t target_oid, iscsi_hba_t *ihp, iscsi_sess_t **ispp) { int rval = 0; iscsi_sess_t *isp = NULL; ASSERT(ihp != NULL); ASSERT(ispp != NULL); /* See if we already created this session */ for (isp = ihp->hba_sess_list; isp; isp = isp->sess_next) { /* * Look for a session associated to the given target. * Return the first one found. */ if (isp->sess_target_oid == target_oid) { /* Found matching session */ break; } } /* If not null this session is already available */ if (isp != NULL) { /* Existing session, return it */ *ispp = isp; } else { rval = EFAULT; } return (rval); } static void iscsi_sess_update_busy_luns(iscsi_sess_t *isp, boolean_t clear) { iscsi_lun_t *ilp; iscsi_hba_t *ihp; ASSERT(isp != NULL); ihp = isp->sess_hba; ASSERT(ihp != NULL); rw_enter(&isp->sess_lun_list_rwlock, RW_WRITER); ilp = isp->sess_lun_list; while (ilp != NULL) { if (clear == B_TRUE) { ilp->lun_state &= ~ISCSI_LUN_STATE_BUSY; } else { ilp->lun_state |= ISCSI_LUN_STATE_BUSY; } ilp = ilp->lun_next; } rw_exit(&isp->sess_lun_list_rwlock); } /* * Submits the scsi enumeration request. Returns * ISCSI_SESS_ENUM_SUBMITTED upon success, or others if failures are met. * If the request is submitted and the wait is set to B_TRUE, the caller * must call iscsi_sess_enum_query at a later time to unblock next enum */ iscsi_enum_result_t iscsi_sess_enum_request(iscsi_sess_t *isp, boolean_t wait, uint32_t event_count) { iscsi_task_t *itp; itp = kmem_zalloc(sizeof (iscsi_task_t), KM_SLEEP); itp->t_arg = isp; itp->t_event_count = event_count; mutex_enter(&isp->sess_enum_lock); while ((isp->sess_enum_status != ISCSI_SESS_ENUM_FREE) && (isp->sess_enum_status != ISCSI_SESS_ENUM_INPROG)) { cv_wait(&isp->sess_enum_cv, &isp->sess_enum_lock); } if (isp->sess_enum_status == ISCSI_SESS_ENUM_INPROG) { /* easy case */ if (wait == B_TRUE) { isp->sess_enum_result_count ++; } mutex_exit(&isp->sess_enum_lock); kmem_free(itp, sizeof (iscsi_task_t)); return (ISCSI_SESS_ENUM_SUBMITTED); } ASSERT(isp->sess_enum_status == ISCSI_SESS_ENUM_FREE); ASSERT(isp->sess_enum_result_count == 0); isp->sess_enum_status = ISCSI_SESS_ENUM_INPROG; if (ddi_taskq_dispatch(isp->sess_enum_taskq, iscsi_sess_enumeration, itp, DDI_SLEEP) != DDI_SUCCESS) { isp->sess_enum_status = ISCSI_SESS_ENUM_FREE; mutex_exit(&isp->sess_enum_lock); kmem_free(itp, sizeof (iscsi_task_t)); return (ISCSI_SESS_ENUM_SUBFAIL); } if (wait == B_TRUE) { isp->sess_enum_result_count ++; } mutex_exit(&isp->sess_enum_lock); return (ISCSI_SESS_ENUM_SUBMITTED); } /* * Wait and query the result of the enumeration. * The last caller is responsible for kicking off the DONE status */ iscsi_enum_result_t iscsi_sess_enum_query(iscsi_sess_t *isp) { iscsi_enum_result_t ret = ISCSI_SESS_ENUM_IOFAIL; mutex_enter(&isp->sess_enum_lock); while (isp->sess_enum_status != ISCSI_SESS_ENUM_DONE) { cv_wait(&isp->sess_enum_cv, &isp->sess_enum_lock); } ret = isp->sess_enum_result; isp->sess_enum_result_count --; if (isp->sess_enum_result_count == 0) { isp->sess_enum_status = ISCSI_SESS_ENUM_FREE; cv_broadcast(&isp->sess_enum_cv); } mutex_exit(&isp->sess_enum_lock); return (ret); } static void iscsi_sess_enum_warn(iscsi_sess_t *isp, iscsi_enum_result_t r) { cmn_err(CE_WARN, "iscsi session (%u) enumeration fails - %s", isp->sess_oid, iscsi_sess_enum_warn_msgs[r]); } void iscsi_sess_enter_state_zone(iscsi_sess_t *isp) { mutex_enter(&isp->sess_state_wmutex); while (isp->sess_state_hasw == B_TRUE) { cv_wait(&isp->sess_state_wcv, &isp->sess_state_wmutex); } isp->sess_state_hasw = B_TRUE; mutex_exit(&isp->sess_state_wmutex); rw_enter(&isp->sess_state_rwlock, RW_WRITER); } void iscsi_sess_exit_state_zone(iscsi_sess_t *isp) { rw_exit(&isp->sess_state_rwlock); mutex_enter(&isp->sess_state_wmutex); isp->sess_state_hasw = B_FALSE; cv_signal(&isp->sess_state_wcv); mutex_exit(&isp->sess_state_wmutex); }