/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * * iSCSI connection interfaces */ #define ISCSI_ICS_NAMES #include "iscsi.h" #include "persistent.h" #include extern ib_boot_prop_t *iscsiboot_prop; static void iscsi_client_notify_task(void *cn_task_void); static void iscsi_conn_flush_active_cmds(iscsi_conn_t *icp); #define SHUTDOWN_TIMEOUT 180 /* seconds */ extern int modrootloaded; boolean_t iscsi_conn_logging = B_FALSE; #define ISCSI_LOGIN_TPGT_NEGO_ERROR(icp) \ (((icp)->conn_login_state == LOGIN_ERROR) && \ ((icp)->conn_login_status == ISCSI_STATUS_LOGIN_TPGT_NEGO_FAIL)) /* * +--------------------------------------------------------------------+ * | External Connection Interfaces | * +--------------------------------------------------------------------+ */ /* * iscsi_conn_create - This creates an iscsi connection structure and * associates it with a session structure. The session's sess_conn_list_rwlock * should be held as a writer before calling this function. */ iscsi_status_t iscsi_conn_create(struct sockaddr *addr, iscsi_sess_t *isp, iscsi_conn_t **icpp) { iscsi_conn_t *icp = NULL; char th_name[ISCSI_TH_MAX_NAME_LEN]; /* See if this connection already exists */ for (icp = isp->sess_conn_list; icp; icp = icp->conn_next) { /* * Compare the ioctl information to see if * its a match for this connection. (This * is done by making sure the IPs are of * the same size and then they are the * same value. */ if (bcmp(&icp->conn_base_addr, addr, SIZEOF_SOCKADDR(addr)) == 0) { /* It's a match, record this connection */ break; } } /* If icp is found return it */ if (icp != NULL) { *icpp = icp; return (ISCSI_STATUS_SUCCESS); } /* We are creating the connection, allocate, and setup */ icp = (iscsi_conn_t *)kmem_zalloc(sizeof (iscsi_conn_t), KM_SLEEP); /* * Setup connection */ icp->conn_sig = ISCSI_SIG_CONN; icp->conn_state = ISCSI_CONN_STATE_FREE; mutex_init(&icp->conn_state_mutex, NULL, MUTEX_DRIVER, NULL); cv_init(&icp->conn_state_change, NULL, CV_DRIVER, NULL); mutex_init(&icp->conn_login_mutex, NULL, MUTEX_DRIVER, NULL); cv_init(&icp->conn_login_cv, NULL, CV_DRIVER, NULL); icp->conn_state_destroy = B_FALSE; idm_sm_audit_init(&icp->conn_state_audit); icp->conn_sess = isp; mutex_enter(&iscsi_oid_mutex); icp->conn_oid = iscsi_oid++; mutex_exit(&iscsi_oid_mutex); /* * IDM CN taskq */ if (snprintf(th_name, sizeof (th_name) - 1, ISCSI_CONN_CN_TASKQ_NAME_FORMAT, icp->conn_sess->sess_hba->hba_oid, icp->conn_sess->sess_oid, icp->conn_oid) >= sizeof (th_name)) { cv_destroy(&icp->conn_state_change); mutex_destroy(&icp->conn_state_mutex); kmem_free(icp, sizeof (iscsi_conn_t)); *icpp = NULL; return (ISCSI_STATUS_INTERNAL_ERROR); } icp->conn_cn_taskq = ddi_taskq_create(icp->conn_sess->sess_hba->hba_dip, th_name, 1, TASKQ_DEFAULTPRI, 0); if (icp->conn_cn_taskq == NULL) { cv_destroy(&icp->conn_state_change); mutex_destroy(&icp->conn_state_mutex); kmem_free(icp, sizeof (iscsi_conn_t)); *icpp = NULL; return (ISCSI_STATUS_INTERNAL_ERROR); } /* Creation of the transfer thread */ if (snprintf(th_name, sizeof (th_name) - 1, ISCSI_CONN_TXTH_NAME_FORMAT, icp->conn_sess->sess_hba->hba_oid, icp->conn_sess->sess_oid, icp->conn_oid) >= sizeof (th_name)) { cv_destroy(&icp->conn_state_change); mutex_destroy(&icp->conn_state_mutex); kmem_free(icp, sizeof (iscsi_conn_t)); ddi_taskq_destroy(icp->conn_cn_taskq); *icpp = NULL; return (ISCSI_STATUS_INTERNAL_ERROR); } icp->conn_tx_thread = iscsi_thread_create(isp->sess_hba->hba_dip, th_name, iscsi_tx_thread, icp); /* setup connection queues */ iscsi_init_queue(&icp->conn_queue_active); iscsi_init_queue(&icp->conn_queue_idm_aborting); bcopy(addr, &icp->conn_base_addr, sizeof (icp->conn_base_addr)); /* Add new connection to the session connection list */ icp->conn_cid = isp->sess_conn_next_cid++; if (isp->sess_conn_list == NULL) { isp->sess_conn_list = isp->sess_conn_list_last_ptr = icp; } else { isp->sess_conn_list_last_ptr->conn_next = icp; isp->sess_conn_list_last_ptr = icp; } KSTAT_INC_SESS_CNTR_CONN(isp); (void) iscsi_conn_kstat_init(icp); *icpp = icp; return (ISCSI_STATUS_SUCCESS); } /* * iscsi_conn_online - This attempts to take a connection from * ISCSI_CONN_STATE_FREE to ISCSI_CONN_STATE_LOGGED_IN. */ iscsi_status_t iscsi_conn_online(iscsi_conn_t *icp) { iscsi_task_t *itp; iscsi_status_t rval; ASSERT(icp != NULL); ASSERT(mutex_owned(&icp->conn_state_mutex)); ASSERT(icp->conn_state == ISCSI_CONN_STATE_FREE); /* * If we are attempting to connect then for the purposes of the * other initiator code we are effectively in ISCSI_CONN_STATE_IN_LOGIN. */ iscsi_conn_update_state_locked(icp, ISCSI_CONN_STATE_IN_LOGIN); mutex_exit(&icp->conn_state_mutex); /* * Sync base connection information before login * A login redirection might have shifted the * current information from the base. */ bcopy(&icp->conn_base_addr, &icp->conn_curr_addr, sizeof (icp->conn_curr_addr)); itp = kmem_zalloc(sizeof (iscsi_task_t), KM_SLEEP); ASSERT(itp != NULL); itp->t_arg = icp; itp->t_blocking = B_TRUE; rval = iscsi_login_start(itp); kmem_free(itp, sizeof (iscsi_task_t)); mutex_enter(&icp->conn_state_mutex); return (rval); } /* * iscsi_conn_offline - This attempts to take a connection from * any state to ISCSI_CONN_STATE_FREE. */ iscsi_status_t iscsi_conn_offline(iscsi_conn_t *icp) { clock_t delay; ASSERT(icp != NULL); /* * We can only destroy a connection if its either in * a state of FREE or LOGGED. The other states are * transitionary and its unsafe to perform actions * on the connection in those states. Set a flag * on the connection to influence the transitions * to quickly complete. Then wait for a state * transition. * * ISCSI_CONN_STATE_LOGGED_IN is set immediately at the * start of CN_NOTIFY_FFP processing. icp->conn_state_ffp * is set to true at the end of ffp processing, at which * point any session updates are complete. We don't * want to start offlining the connection before we're * done completing the FFP processing since this might * interrupt the discovery process. */ delay = ddi_get_lbolt() + SEC_TO_TICK(SHUTDOWN_TIMEOUT); mutex_enter(&icp->conn_state_mutex); icp->conn_state_destroy = B_TRUE; while ((((icp->conn_state != ISCSI_CONN_STATE_FREE) && (icp->conn_state != ISCSI_CONN_STATE_LOGGED_IN)) || ((icp->conn_state == ISCSI_CONN_STATE_LOGGED_IN) && !icp->conn_state_ffp)) && (ddi_get_lbolt() < delay)) { /* wait for transition */ (void) cv_timedwait(&icp->conn_state_change, &icp->conn_state_mutex, delay); } switch (icp->conn_state) { case ISCSI_CONN_STATE_FREE: break; case ISCSI_CONN_STATE_LOGGED_IN: if (icp->conn_state_ffp) { /* Hold is released in iscsi_handle_logout */ idm_conn_hold(icp->conn_ic); (void) iscsi_handle_logout(icp); } else { icp->conn_state_destroy = B_FALSE; mutex_exit(&icp->conn_state_mutex); return (ISCSI_STATUS_INTERNAL_ERROR); } break; case ISCSI_CONN_STATE_IN_LOGIN: case ISCSI_CONN_STATE_IN_LOGOUT: case ISCSI_CONN_STATE_FAILED: case ISCSI_CONN_STATE_POLLING: default: icp->conn_state_destroy = B_FALSE; mutex_exit(&icp->conn_state_mutex); return (ISCSI_STATUS_INTERNAL_ERROR); } mutex_exit(&icp->conn_state_mutex); return (ISCSI_STATUS_SUCCESS); } /* * iscsi_conn_destroy - This destroys an iscsi connection structure * and de-associates it with the session. The connection should * already been in the ISCSI_CONN_STATE_FREE when attempting this * operation. */ iscsi_status_t iscsi_conn_destroy(iscsi_conn_t *icp) { iscsi_sess_t *isp; iscsi_conn_t *t_icp; ASSERT(icp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); if (icp->conn_state != ISCSI_CONN_STATE_FREE) { return (ISCSI_STATUS_INTERNAL_ERROR); } /* Destroy transfer thread */ iscsi_thread_destroy(icp->conn_tx_thread); ddi_taskq_destroy(icp->conn_cn_taskq); /* Terminate connection queues */ iscsi_destroy_queue(&icp->conn_queue_idm_aborting); iscsi_destroy_queue(&icp->conn_queue_active); cv_destroy(&icp->conn_login_cv); mutex_destroy(&icp->conn_login_mutex); cv_destroy(&icp->conn_state_change); mutex_destroy(&icp->conn_state_mutex); /* * Remove connection from sessions linked list. */ if (isp->sess_conn_list == icp) { /* connection first item in list */ isp->sess_conn_list = icp->conn_next; /* * check if this is also the last item in the list */ if (isp->sess_conn_list_last_ptr == icp) { isp->sess_conn_list_last_ptr = NULL; } } else { /* * search session list for icp pointing * to connection being removed. Then * update that connections next pointer. */ t_icp = isp->sess_conn_list; while (t_icp->conn_next != NULL) { if (t_icp->conn_next == icp) { break; } t_icp = t_icp->conn_next; } if (t_icp->conn_next == icp) { t_icp->conn_next = icp->conn_next; /* * if this is the last connection in the list * update the last_ptr to point to t_icp */ if (isp->sess_conn_list_last_ptr == icp) { isp->sess_conn_list_last_ptr = t_icp; } } else { /* couldn't find session */ ASSERT(FALSE); } } /* Free this Connections Data */ iscsi_conn_kstat_term(icp); kmem_free(icp, sizeof (iscsi_conn_t)); return (ISCSI_STATUS_SUCCESS); } /* * iscsi_conn_set_login_min_max - set min/max login window * * Used to set the min and max login window. Input values * are in seconds. */ void iscsi_conn_set_login_min_max(iscsi_conn_t *icp, int min, int max) { ASSERT(icp != NULL); icp->conn_login_min = ddi_get_lbolt() + SEC_TO_TICK(min); icp->conn_login_max = ddi_get_lbolt() + SEC_TO_TICK(max); } /* * Process the idm notifications */ idm_status_t iscsi_client_notify(idm_conn_t *ic, idm_client_notify_t icn, uintptr_t data) { iscsi_cn_task_t *cn; iscsi_conn_t *icp = ic->ic_handle; iscsi_sess_t *isp; uint32_t event_count; /* * Don't access icp if the notification is CN_CONNECT_DESTROY * since icp may have already been freed. * * In particular, we cannot audit the CN_CONNECT_DESTROY event. * * Handle a few cases immediately, the rest in a task queue. */ switch (icn) { case CN_CONNECT_FAIL: case CN_LOGIN_FAIL: /* * Wakeup any thread waiting for login stuff to happen. */ ASSERT(icp != NULL); mutex_enter(&icp->conn_state_mutex); idm_sm_audit_event(&icp->conn_state_audit, SAS_ISCSI_CONN, icp->conn_state, icn, data); mutex_exit(&icp->conn_state_mutex); iscsi_login_update_state(icp, LOGIN_ERROR); return (IDM_STATUS_SUCCESS); case CN_READY_FOR_LOGIN: idm_conn_hold(ic); /* Released in CN_CONNECT_LOST */ ASSERT(icp != NULL); mutex_enter(&icp->conn_state_mutex); idm_sm_audit_event(&icp->conn_state_audit, SAS_ISCSI_CONN, icp->conn_state, icn, data); icp->conn_state_idm_connected = B_TRUE; cv_broadcast(&icp->conn_state_change); mutex_exit(&icp->conn_state_mutex); iscsi_login_update_state(icp, LOGIN_READY); return (IDM_STATUS_SUCCESS); case CN_CONNECT_DESTROY: /* * We released any dependecies we had on this object in * either CN_LOGIN_FAIL or CN_CONNECT_LOST so we just need * to destroy the IDM connection now. */ idm_ini_conn_destroy(ic); return (IDM_STATUS_SUCCESS); } ASSERT(icp != NULL); mutex_enter(&icp->conn_state_mutex); idm_sm_audit_event(&icp->conn_state_audit, SAS_ISCSI_CONN, icp->conn_state, icn, data); mutex_exit(&icp->conn_state_mutex); isp = icp->conn_sess; /* * Dispatch notifications to the taskq since they often require * long blocking operations. In the case of CN_CONNECT_DESTROY * we actually just want to destroy the connection which we * can't do in the IDM taskq context. */ cn = kmem_alloc(sizeof (*cn), KM_SLEEP); cn->ct_ic = ic; cn->ct_icn = icn; cn->ct_data = data; idm_conn_hold(ic); if (ddi_taskq_dispatch(icp->conn_cn_taskq, iscsi_client_notify_task, cn, DDI_SLEEP) != DDI_SUCCESS) { idm_conn_rele(ic); cmn_err(CE_WARN, "iscsi connection(%u) failure - " "unable to schedule notify task", icp->conn_oid); iscsi_conn_update_state(icp, ISCSI_CONN_STATE_FREE); event_count = atomic_inc_32_nv(&isp->sess_state_event_count); iscsi_sess_enter_state_zone(isp); iscsi_sess_state_machine(isp, ISCSI_SESS_EVENT_N6, event_count); iscsi_sess_exit_state_zone(isp); } return (IDM_STATUS_SUCCESS); } static void iscsi_client_notify_task(void *cn_task_void) { iscsi_cn_task_t *cn_task = cn_task_void; iscsi_conn_t *icp; iscsi_sess_t *isp; idm_conn_t *ic; idm_client_notify_t icn; uintptr_t data; idm_ffp_disable_t disable_type; boolean_t in_login; uint32_t event_count; ic = cn_task->ct_ic; icn = cn_task->ct_icn; data = cn_task->ct_data; icp = ic->ic_handle; ASSERT(icp != NULL); isp = icp->conn_sess; switch (icn) { case CN_FFP_ENABLED: mutex_enter(&icp->conn_state_mutex); icp->conn_async_logout = B_FALSE; icp->conn_state_ffp = B_TRUE; cv_broadcast(&icp->conn_state_change); mutex_exit(&icp->conn_state_mutex); /* * This logic assumes that the IDM login-snooping code * and the initiator login code will agree to go when * the connection is in FFP or final error received. * The reason we do this is that we don't want to process * CN_FFP_DISABLED until CN_FFP_ENABLED has been full handled. */ mutex_enter(&icp->conn_login_mutex); while ((icp->conn_login_state != LOGIN_FFP) && (icp->conn_login_state != LOGIN_ERROR)) { cv_wait(&icp->conn_login_cv, &icp->conn_login_mutex); } mutex_exit(&icp->conn_login_mutex); break; case CN_FFP_DISABLED: disable_type = (idm_ffp_disable_t)data; mutex_enter(&icp->conn_state_mutex); switch (disable_type) { case FD_SESS_LOGOUT: case FD_CONN_LOGOUT: if (icp->conn_async_logout) { /* * Our logout was in response to an * async logout request so treat this * like a connection failure (we will * try to re-establish the connection) */ iscsi_conn_update_state_locked(icp, ISCSI_CONN_STATE_FAILED); } else { /* * Logout due to to user config change, * we will not try to re-establish * the connection. */ iscsi_conn_update_state_locked(icp, ISCSI_CONN_STATE_IN_LOGOUT); /* * Hold off generating the ISCSI_SESS_EVENT_N3 * event until we get the CN_CONNECT_LOST * notification. This matches the pre-IDM * implementation better. */ } break; case FD_CONN_FAIL: default: if (icp->conn_state == ISCSI_CONN_STATE_IN_LOGIN) { iscsi_conn_update_state_locked(icp, ISCSI_CONN_STATE_FREE); } else { iscsi_conn_update_state_locked(icp, ISCSI_CONN_STATE_FAILED); } break; } icp->conn_state_ffp = B_FALSE; cv_broadcast(&icp->conn_state_change); mutex_exit(&icp->conn_state_mutex); break; case CN_CONNECT_LOST: /* * We only care about CN_CONNECT_LOST if we've logged in. IDM * sends a flag as the data payload to indicate whether we * were trying to login. The CN_LOGIN_FAIL notification * gives us what we need to know for login failures and * otherwise we will need to keep a bunch of state to know * what CN_CONNECT_LOST means to us. */ in_login = (boolean_t)data; if (in_login || (icp->conn_prev_state == ISCSI_CONN_STATE_IN_LOGIN)) { mutex_enter(&icp->conn_state_mutex); icp->conn_state_idm_connected = B_FALSE; cv_broadcast(&icp->conn_state_change); mutex_exit(&icp->conn_state_mutex); /* Release connect hold from CN_READY_FOR_LOGIN */ idm_conn_rele(ic); break; } /* Any remaining commands are never going to finish */ iscsi_conn_flush_active_cmds(icp); /* * The connection is no longer active so cleanup any * references to the connection and release any holds so * that IDM can finish cleanup. */ mutex_enter(&icp->conn_state_mutex); if (icp->conn_state != ISCSI_CONN_STATE_FAILED) { mutex_exit(&icp->conn_state_mutex); event_count = atomic_inc_32_nv( &isp->sess_state_event_count); iscsi_sess_enter_state_zone(isp); iscsi_sess_state_machine(isp, ISCSI_SESS_EVENT_N3, event_count); iscsi_sess_exit_state_zone(isp); mutex_enter(&icp->conn_state_mutex); iscsi_conn_update_state_locked(icp, ISCSI_CONN_STATE_FREE); } else { mutex_exit(&icp->conn_state_mutex); event_count = atomic_inc_32_nv( &isp->sess_state_event_count); iscsi_sess_enter_state_zone(isp); iscsi_sess_state_machine(isp, ISCSI_SESS_EVENT_N5, event_count); iscsi_sess_exit_state_zone(isp); /* * If session type is NORMAL, try to reestablish the * connection. */ if ((isp->sess_type == ISCSI_SESS_TYPE_NORMAL) && !(ISCSI_LOGIN_TPGT_NEGO_ERROR(icp))) { iscsi_conn_retry(isp, icp); mutex_enter(&icp->conn_state_mutex); } else { event_count = atomic_inc_32_nv( &isp->sess_state_event_count); iscsi_sess_enter_state_zone(isp); iscsi_sess_state_machine(isp, ISCSI_SESS_EVENT_N6, event_count); iscsi_sess_exit_state_zone(isp); mutex_enter(&icp->conn_state_mutex); iscsi_conn_update_state_locked(icp, ISCSI_CONN_STATE_FREE); } } (void) iscsi_thread_stop(icp->conn_tx_thread); icp->conn_state_idm_connected = B_FALSE; cv_broadcast(&icp->conn_state_change); mutex_exit(&icp->conn_state_mutex); /* Release connect hold from CN_READY_FOR_LOGIN */ idm_conn_rele(ic); break; default: ISCSI_CONN_LOG(CE_WARN, "iscsi_client_notify: unknown notification: " "%x: NOT IMPLEMENTED YET: icp: %p ic: %p ", icn, (void *)icp, (void *)ic); break; } /* free the task notify structure we allocated in iscsi_client_notify */ kmem_free(cn_task, sizeof (*cn_task)); /* Release the hold we acquired in iscsi_client_notify */ idm_conn_rele(ic); } /* * iscsi_conn_sync_params - used to update connection parameters * * Used to update connection parameters with current configured * parameters in the persistent store. This should be called * before starting to make a new iscsi connection in iscsi_login. */ iscsi_status_t iscsi_conn_sync_params(iscsi_conn_t *icp) { iscsi_sess_t *isp; iscsi_hba_t *ihp; int param_id; persistent_param_t pp; persistent_tunable_param_t ptp; iscsi_config_sess_t *ics; int idx, size; char *name; ASSERT(icp != NULL); ASSERT((icp->conn_state == ISCSI_CONN_STATE_IN_LOGIN) || (icp->conn_state == ISCSI_CONN_STATE_FAILED) || (icp->conn_state == ISCSI_CONN_STATE_POLLING)); isp = icp->conn_sess; ASSERT(isp != NULL); ihp = isp->sess_hba; ASSERT(ihp != NULL); /* * Check if someone is trying to destroy this * connection. If so fail the sync request, * as a method of fast fail. */ if (icp->conn_state_destroy == B_TRUE) { return (ISCSI_STATUS_SHUTDOWN); } bzero(&pp, sizeof (pp)); /* First get a copy of the HBA params */ bcopy(&ihp->hba_params, &icp->conn_params, sizeof (iscsi_login_params_t)); bcopy(&ihp->hba_tunable_params, &icp->conn_tunable_params, sizeof (iscsi_tunable_params_t)); /* * Now we need to get the session configured * values from the persistent store and apply * them to our connection. */ (void) persistent_param_get((char *)isp->sess_name, &pp); for (param_id = 0; param_id < ISCSI_NUM_LOGIN_PARAM; param_id++) { if (iscsiboot_prop && modrootloaded && !iscsi_chk_bootlun_mpxio(ihp) && isp->sess_boot) { /* * iscsi boot with mpxio disabled * while iscsi booting target's parameter overriden * do no update target's parameters. */ if (pp.p_bitmap) { cmn_err(CE_NOTE, "Adopting " " default login parameters in" " boot session as MPxIO is disabled"); } break; } if (pp.p_bitmap & (1 << param_id)) { switch (param_id) { /* * Boolean parameters */ case ISCSI_LOGIN_PARAM_DATA_SEQUENCE_IN_ORDER: icp->conn_params.data_pdu_in_order = pp.p_params.data_pdu_in_order; break; case ISCSI_LOGIN_PARAM_IMMEDIATE_DATA: icp->conn_params.immediate_data = pp.p_params.immediate_data; break; case ISCSI_LOGIN_PARAM_INITIAL_R2T: icp->conn_params.initial_r2t = pp.p_params.initial_r2t; break; case ISCSI_LOGIN_PARAM_DATA_PDU_IN_ORDER: icp->conn_params.data_pdu_in_order = pp.p_params.data_pdu_in_order; break; /* * Integer parameters */ case ISCSI_LOGIN_PARAM_HEADER_DIGEST: icp->conn_params.header_digest = pp.p_params.header_digest; break; case ISCSI_LOGIN_PARAM_DATA_DIGEST: icp->conn_params.data_digest = pp.p_params.data_digest; break; case ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_RETAIN: icp->conn_params.default_time_to_retain = pp.p_params.default_time_to_retain; break; case ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_WAIT: icp->conn_params.default_time_to_wait = pp.p_params.default_time_to_wait; break; case ISCSI_LOGIN_PARAM_MAX_RECV_DATA_SEGMENT_LENGTH: icp->conn_params.max_recv_data_seg_len = pp.p_params.max_recv_data_seg_len; break; case ISCSI_LOGIN_PARAM_FIRST_BURST_LENGTH: icp->conn_params.first_burst_length = pp.p_params.first_burst_length; break; case ISCSI_LOGIN_PARAM_MAX_BURST_LENGTH: icp->conn_params.max_burst_length = pp.p_params.max_burst_length; break; /* * Integer parameters which currently are unsettable */ case ISCSI_LOGIN_PARAM_MAX_CONNECTIONS: /* FALLTHRU */ case ISCSI_LOGIN_PARAM_OUTSTANDING_R2T: /* FALLTHRU */ case ISCSI_LOGIN_PARAM_ERROR_RECOVERY_LEVEL: /* FALLTHRU */ default: break; } } } if (persistent_get_tunable_param((char *)isp->sess_name, &ptp) == B_TRUE) { if (ptp.p_bitmap & ISCSI_TUNABLE_PARAM_RX_TIMEOUT_VALUE) { icp->conn_tunable_params.recv_login_rsp_timeout = ptp.p_params.recv_login_rsp_timeout; } if (ptp.p_bitmap & ISCSI_TUNABLE_PARAM_CONN_LOGIN_MAX) { icp->conn_tunable_params.conn_login_max = ptp.p_params.conn_login_max; } if (ptp.p_bitmap & ISCSI_TUNABLE_PARAM_LOGIN_POLLING_DELAY) { icp->conn_tunable_params.polling_login_delay = ptp.p_params.polling_login_delay; } } /* Skip binding checks on discovery sessions */ if (isp->sess_type == ISCSI_SESS_TYPE_DISCOVERY) { return (ISCSI_STATUS_SUCCESS); } /* * Now we need to get the current optional connection * binding information. */ /* setup initial buffer for configured session information */ size = sizeof (*ics); ics = kmem_zalloc(size, KM_SLEEP); ics->ics_in = 1; /* get configured sessions information */ name = (char *)isp->sess_name; if (persistent_get_config_session(name, ics) == B_FALSE) { /* * If we were unable to get target level information * then check the initiator level information. */ name = (char *)isp->sess_hba->hba_name; if (persistent_get_config_session(name, ics) == B_FALSE) { /* * No hba information is found. So assume default * one session unbound behavior. */ ics->ics_out = 1; ics->ics_bound = B_FALSE; } } if (iscsiboot_prop && (ics->ics_out > 1) && isp->sess_boot && !iscsi_chk_bootlun_mpxio(ihp)) { /* * iscsi booting session with mpxio disabled, * no need set multiple sessions for booting session */ ics->ics_out = 1; ics->ics_bound = B_FALSE; cmn_err(CE_NOTE, "MPxIO is disabled," " no need to configure multiple boot sessions"); } /* * Check to make sure this session is still a configured * session. The user might have decreased the session * count. (NOTE: byte 5 of the sess_isid is the session * count (via MS/T). This counter starts at 0.) */ idx = isp->sess_isid[5]; if (iscsiboot_prop && (idx == ISCSI_MAX_CONFIG_SESSIONS)) { /* * This is temporary session for boot session propose * no need to bound IP for this session */ icp->conn_bound = B_FALSE; kmem_free(ics, sizeof (iscsi_config_sess_t)); return (ISCSI_STATUS_SUCCESS); } if (ics->ics_out <= idx) { /* * No longer a configured session. Return a * failure so we don't attempt to relogin. */ return (ISCSI_STATUS_SHUTDOWN); } /* * If sessions are unbound set this information on * the connection and return success. */ if (ics->ics_bound == B_FALSE) { icp->conn_bound = B_FALSE; kmem_free(ics, sizeof (iscsi_config_sess_t)); return (ISCSI_STATUS_SUCCESS); } /* * Since the sessions are bound we need to find the matching * binding information for the session's isid. If this * session's isid is > 0 then we need to get more configured * session information to find the binding info. */ if (idx > 0) { int ics_out; ics_out = ics->ics_out; /* record new size and free last buffer */ size = ISCSI_SESSION_CONFIG_SIZE(ics_out); kmem_free(ics, sizeof (*ics)); /* allocate new buffer */ ics = kmem_zalloc(size, KM_SLEEP); ics->ics_in = ics_out; /* get configured sessions information */ if (persistent_get_config_session(name, ics) != B_TRUE) { cmn_err(CE_NOTE, "iscsi session(%d) - " "unable to get configured session information\n", isp->sess_oid); kmem_free(ics, size); return (ISCSI_STATUS_SHUTDOWN); } } /* Copy correct binding information to the connection */ icp->conn_bound = B_TRUE; if (ics->ics_bindings[idx].i_insize == sizeof (struct in_addr)) { bcopy(&ics->ics_bindings[idx].i_addr.in4, &icp->conn_bound_addr.sin4.sin_addr.s_addr, sizeof (struct in_addr)); icp->conn_bound_addr.sin4.sin_family = AF_INET; } else { bcopy(&ics->ics_bindings[idx].i_addr.in6, &icp->conn_bound_addr.sin6.sin6_addr.s6_addr, sizeof (struct in6_addr)); icp->conn_bound_addr.sin6.sin6_family = AF_INET6; } kmem_free(ics, size); return (ISCSI_STATUS_SUCCESS); } /* * +--------------------------------------------------------------------+ * | Internal Connection Interfaces | * +--------------------------------------------------------------------+ */ /* * iscsi_conn_flush_active_cmds - flush all active icmdps * for a connection. */ static void iscsi_conn_flush_active_cmds(iscsi_conn_t *icp) { iscsi_cmd_t *icmdp; iscsi_sess_t *isp; boolean_t lock_held = B_FALSE; ASSERT(icp != NULL); isp = icp->conn_sess; ASSERT(isp != NULL); if (mutex_owned(&icp->conn_queue_active.mutex)) { lock_held = B_TRUE; } else { mutex_enter(&icp->conn_queue_active.mutex); } /* Flush active queue */ icmdp = icp->conn_queue_active.head; while (icmdp != NULL) { mutex_enter(&icmdp->cmd_mutex); if (icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) { icmdp->cmd_un.scsi.pkt_stat |= STAT_ABORTED; } mutex_exit(&icmdp->cmd_mutex); iscsi_cmd_state_machine(icmdp, ISCSI_CMD_EVENT_E7, isp); icmdp = icp->conn_queue_active.head; } /* Wait for active queue to drain */ while (icp->conn_queue_active.count) { mutex_exit(&icp->conn_queue_active.mutex); delay(drv_usectohz(100000)); mutex_enter(&icp->conn_queue_active.mutex); } if (lock_held == B_FALSE) { mutex_exit(&icp->conn_queue_active.mutex); } /* Wait for IDM abort queue to drain (if necessary) */ mutex_enter(&icp->conn_queue_idm_aborting.mutex); while (icp->conn_queue_idm_aborting.count) { mutex_exit(&icp->conn_queue_idm_aborting.mutex); delay(drv_usectohz(100000)); mutex_enter(&icp->conn_queue_idm_aborting.mutex); } mutex_exit(&icp->conn_queue_idm_aborting.mutex); } /* * iscsi_conn_retry - retry connect/login */ void iscsi_conn_retry(iscsi_sess_t *isp, iscsi_conn_t *icp) { iscsi_task_t *itp; uint32_t event_count; ASSERT(isp != NULL); ASSERT(icp != NULL); /* set login min/max time values */ iscsi_conn_set_login_min_max(icp, ISCSI_CONN_DEFAULT_LOGIN_MIN, icp->conn_tunable_params.conn_login_max); ISCSI_CONN_LOG(CE_NOTE, "DEBUG: iscsi_conn_retry: icp: %p icp: %p ", (void *)icp, (void *)icp->conn_ic); /* * Sync base connection information before login. * A login redirection might have shifted the * current information from the base. */ bcopy(&icp->conn_base_addr, &icp->conn_curr_addr, sizeof (icp->conn_curr_addr)); /* schedule login task */ itp = kmem_zalloc(sizeof (iscsi_task_t), KM_SLEEP); itp->t_arg = icp; itp->t_blocking = B_FALSE; if (ddi_taskq_dispatch(isp->sess_login_taskq, iscsi_login_cb, itp, DDI_SLEEP) != DDI_SUCCESS) { kmem_free(itp, sizeof (iscsi_task_t)); cmn_err(CE_WARN, "iscsi connection(%u) failure - " "unable to schedule login task", icp->conn_oid); iscsi_conn_update_state(icp, ISCSI_CONN_STATE_FREE); event_count = atomic_inc_32_nv( &isp->sess_state_event_count); iscsi_sess_enter_state_zone(isp); iscsi_sess_state_machine(isp, ISCSI_SESS_EVENT_N6, event_count); iscsi_sess_exit_state_zone(isp); } } void iscsi_conn_update_state(iscsi_conn_t *icp, iscsi_conn_state_t next_state) { mutex_enter(&icp->conn_state_mutex); (void) iscsi_conn_update_state_locked(icp, next_state); mutex_exit(&icp->conn_state_mutex); } void iscsi_conn_update_state_locked(iscsi_conn_t *icp, iscsi_conn_state_t next_state) { ASSERT(mutex_owned(&icp->conn_state_mutex)); next_state = (next_state > ISCSI_CONN_STATE_MAX) ? ISCSI_CONN_STATE_MAX : next_state; idm_sm_audit_state_change(&icp->conn_state_audit, SAS_ISCSI_CONN, icp->conn_state, next_state); switch (next_state) { case ISCSI_CONN_STATE_FREE: case ISCSI_CONN_STATE_IN_LOGIN: case ISCSI_CONN_STATE_LOGGED_IN: case ISCSI_CONN_STATE_IN_LOGOUT: case ISCSI_CONN_STATE_FAILED: case ISCSI_CONN_STATE_POLLING: ISCSI_CONN_LOG(CE_NOTE, "iscsi_conn_update_state conn %p %s(%d) -> %s(%d)", (void *)icp, iscsi_ics_name[icp->conn_state], icp->conn_state, iscsi_ics_name[next_state], next_state); icp->conn_prev_state = icp->conn_state; icp->conn_state = next_state; cv_broadcast(&icp->conn_state_change); break; default: cmn_err(CE_WARN, "Update state found illegal state: %x " "prev_state: %x", next_state, icp->conn_prev_state); ASSERT(0); } }