--- /dev/null Fri Apr 4 13:31:05 2008 +++ new/src/sun_nws/comstar/port_providers/iscsit/src/iscsit_sess.c Fri Apr 4 13:31:05 2008 @@ -0,0 +1,731 @@ +/* + * 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 src/sun_nws/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 src/sun_nws/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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "@(#)iscsit_sess.c 1.2 08/03/26 SMI" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* networking stuff */ +#include /* networking stuff */ +#include /* ASSERT3U */ +#include + +#include +#include +#include +#include +#include + + + +typedef struct { + list_node_t se_ctx_node; + iscsit_session_event_t se_ctx_event; + iscsit_conn_t *se_event_data; +} sess_event_ctx_t; + +extern char *iscsit_ss_name[]; +extern char *iscsit_se_name[]; + +static uint16_t +iscsit_get_tsih(void); + +static void +sess_sm_event_locked(iscsit_sess_t *ist, iscsit_session_event_t event, +iscsit_conn_t *ict); + +static void +sess_sm_complete(void *ist_void); + +static void +sess_sm_event_dispatch(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q1_free(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q2_active(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q3_logged_in(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q4_failed(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q5_continue(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q6_done(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q7_error(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_new_state(iscsit_sess_t *ist, sess_event_ctx_t *ctx, + iscsit_session_state_t new_state); + + +static uint16_t +iscsit_tsih_alloc(void) +{ + uintptr_t result; + + result = (uintptr_t)vmem_alloc(iscsit_global.global_tsih_pool, + 1, VM_NOSLEEP | VM_NEXTFIT); + + /* ISCSI_UNSPEC_TSIH (0) indicates failure */ + return (result > ISCSI_MAX_TSIH ? ISCSI_UNSPEC_TSIH : result); +} + +static void +iscsit_tsih_free(uint16_t tsih) +{ + vmem_free(iscsit_global.global_tsih_pool, (void *)(uintptr_t)tsih, 1); +} + + +iscsit_sess_t * +iscsit_sess_create(iscsit_tgt_t *tgt, iscsit_conn_t *ict, + uint32_t cmdsn, uint8_t *isid, + char *initiator_name, char *target_name, + uint8_t *error_class, uint8_t *error_detail) +{ + iscsit_sess_t *result; + + + /* + * Even if this session create "fails" for some reason we still need + * to return a valid session pointer so that we can send the failed + * login response. + */ + result = kmem_zalloc(sizeof (*result), KM_SLEEP); + + /* Allocate TSIH */ + if ((result->ist_tsih = iscsit_tsih_alloc()) == ISCSI_UNSPEC_TSIH) { + /* Out of TSIH's */ + *error_class = ISCSI_STATUS_CLASS_TARGET_ERR; + *error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + /* + * Continue initializing this session so we can use it + * to complete the login process. + */ + } + + mutex_init(&result->ist_mutex, NULL, MUTEX_DEFAULT, NULL); + cv_init(&result->ist_cv, NULL, CV_DEFAULT, NULL); + list_create(&result->ist_events, sizeof (sess_event_ctx_t), + offsetof(sess_event_ctx_t, se_ctx_node)); + list_create(&result->ist_conn_list, sizeof (iscsit_conn_t), + offsetof(iscsit_conn_t, ict_sess_ln)); + + result->ist_state = SS_Q1_FREE; + result->ist_last_state = SS_Q1_FREE; + bcopy(isid, result->ist_isid, ISCSI_ISID_LEN); + + result->ist_tgt = tgt; + result->ist_expcmdsn = cmdsn + 1; + result->ist_maxcmdsn = result->ist_expcmdsn + 1; + + result->ist_initiator_name = + kmem_alloc(strlen(initiator_name) + 1, KM_SLEEP); + strcpy(result->ist_initiator_name, initiator_name); + if (target_name) { + /* A discovery session might not have a target name */ + result->ist_target_name = + kmem_alloc(strlen(target_name) + 1, KM_SLEEP); + strcpy(result->ist_target_name, target_name); + } + + /* Login code will fill in ist_stmf_sess if necessary */ + + /* XXX set the authentication parameter */ + iscsit_sess_set_auth(result); + + /* Kick session state machine (also binds connection to session) */ + iscsit_sess_sm_event(result, SE_CONN_IN_LOGIN, ict); + + *error_class = ISCSI_STATUS_CLASS_SUCCESS; +sess_create_fail: + /* + * As noted above we must return a session pointer even if something + * failed. The resources will get freed later. + */ + return (result); +} + +void +iscsit_sess_destroy(iscsit_sess_t *ist) +{ + if (ist->ist_initiator_name) + kmem_free(ist->ist_initiator_name, + strlen(ist->ist_initiator_name) + 1); + if (ist->ist_target_name) + kmem_free(ist->ist_target_name, + strlen(ist->ist_target_name) + 1); + list_destroy(&ist->ist_conn_list); + list_destroy(&ist->ist_events); + cv_destroy(&ist->ist_cv); + mutex_destroy(&ist->ist_mutex); + if (ist->ist_tsih != ISCSI_UNSPEC_TSIH) { + iscsit_tsih_free(ist->ist_tsih); + } + kmem_free(ist, sizeof (*ist)); +} + + +void +iscsit_sess_bind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict) +{ + ict->ict_sess = ist; + mutex_enter(&ist->ist_mutex); + ist->ist_conn_count++; + list_insert_tail(&ist->ist_conn_list, ict); + mutex_exit(&ist->ist_mutex); +} + +void +iscsit_sess_unbind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict) +{ + mutex_enter(&ist->ist_mutex); + list_remove(&ist->ist_conn_list, ict); + ist->ist_conn_count--; + mutex_exit(&ist->ist_mutex); +} + +iscsit_conn_t * +iscsit_sess_lookup_conn(iscsit_sess_t *ist, uint16_t cid) +{ + iscsit_conn_t *result; + + for (result = list_head(&ist->ist_conn_list); + result != NULL; + result = list_next(&ist->ist_conn_list, result)) { + if (result->ict_cid == cid) + return (result); + } + + return (NULL); +} + +/* + * Set the authentication parameters for the session. + */ +void +iscsit_sess_set_auth(iscsit_sess_t *ist) +{ + char *username, *username_in; + char *password, *password_in; + + /* XXX Load target CHAP name */ + username = "target"; + (void) strcpy(ist->ist_auth.username, username); + + /* XXX Load target CHAP secret */ + password = "987654321098"; + (void) strcpy((char *)ist->ist_auth.password, password); + ist->ist_auth.password_length = strlen(password); + + /* XXX Load initiator CHAP name */ + username_in = "initiator"; + (void) strcpy(ist->ist_auth.username_in, username_in); + + /* XXX Load initiator CHAP secret */ + password_in = "123456789012"; + (void) strcpy((char *)ist->ist_auth.password_in, password_in); + ist->ist_auth.password_length_in = strlen(password_in); + + /* XXX load the applicable authentication methods */ + if (ist->ist_auth.password_length_in != 0) { + ist->ist_auth.authMethodValidList[0] = AM_CHAP; + } else { + ist->ist_auth.authMethodValidList[0] = AM_NONE; + } + ist->ist_auth.authMethodValidList[1] = 0; /* end of list */ +} + + +iscsit_sess_t * +iscsit_sess_reinstate(iscsit_sess_t *ist, iscsit_conn_t *ict, + uint8_t *error_class, uint8_t *error_detail) +{ + iscsit_sess_t *new_sess; + boolean_t error = B_FALSE; + + if (ist->ist_state < SS_Q3_LOGGED_IN) { + /* Reinstatement is not valid at this point */ + *error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + *error_detail = ISCSI_LOGIN_STATUS_INIT_ERR; + /* + * Continue with session initialization since we need to + * return a session + */ + error = B_TRUE; + } + + /* + * Session reinstatement replaces a current session in + * SS_Q4_FAILED state with a new session. The new + * session will have the same ISID as the existing session. + */ + new_sess = iscsit_sess_create(ist->ist_tgt, ict, 0, + ist->ist_isid, ist->ist_initiator_name, ist->ist_target_name, + error_class, error_detail); + ASSERT(new_sess != NULL); + + /* Copy additional fields from original session */ + new_sess->ist_expcmdsn = ist->ist_expcmdsn; + new_sess->ist_maxcmdsn = ist->ist_expcmdsn + 1; + + if (!error) { + mutex_enter(&ist->ist_mutex); + /* + * Generate reinstate event + */ + sess_sm_event_locked(ist, SE_SESSION_REINSTATE, NULL); + mutex_exit(&ist->ist_mutex); + } + + return (new_sess); +} + +int +iscsit_sess_avl_compare(const void *void_sess1, const void *void_sess2) +{ + const iscsit_sess_t *sess1 = void_sess1; + const iscsit_sess_t *sess2 = void_sess2; + int result; + + /* + * Sort by initiator name, then ISID then TSIH + */ + result = strcmp(sess1->ist_initiator_name, sess2->ist_initiator_name); + if (result < 0) { + return (-1); + } else if (result > 0) { + return (1); + } + + /* + * Initiator names match, compare ISIDs + */ + result = memcmp(sess1->ist_isid, sess2->ist_isid, ISCSI_ISID_LEN); + if (result < 0) { + return (-1); + } else if (result > 0) { + return (1); + } + + /* + * ISIDs match, compare TSIHs + */ + if (sess1->ist_tsih < sess2->ist_tsih) { + return (-1); + } else if (sess1->ist_tsih > sess2->ist_tsih) { + return (1); + } + + /* + * Sessions match + */ + return (0); +} + + +/* + * State machine + */ + +void +iscsit_sess_sm_event(iscsit_sess_t *ist, iscsit_session_event_t event, + iscsit_conn_t *ict) +{ + mutex_enter(&ist->ist_mutex); + sess_sm_event_locked(ist, event, ict); + mutex_exit(&ist->ist_mutex); +} + +static void +sess_sm_event_locked(iscsit_sess_t *ist, iscsit_session_event_t event, + iscsit_conn_t *ict) +{ + sess_event_ctx_t *ctx; + + ctx = kmem_zalloc(sizeof (*ctx), KM_SLEEP); + + ctx->se_ctx_event = event; + ctx->se_event_data = ict; + + list_insert_tail(&ist->ist_events, ctx); + /* + * Use the icl_busy flag to keep the state machine single threaded. + * This also serves as recursion avoidance since this flag will + * always be set if we call login_sm_event from within the + * state machine code. + */ + if (!ist->ist_sm_busy) { + ist->ist_sm_busy = B_TRUE; + while (!list_is_empty(&ist->ist_events)) { + ctx = list_head(&ist->ist_events); + list_remove(&ist->ist_events, ctx); + mutex_exit(&ist->ist_mutex); + sess_sm_event_dispatch(ist, ctx); + mutex_enter(&ist->ist_mutex); + } + ist->ist_sm_busy = B_FALSE; + + /* + * When the state machine reaches SS_Q6_DONE or SS_Q7_ERROR + * state the session is over and it's time to cleanup. The + * state machine code will mark itself "complete" when this + * happens. + * + * If we get an "spurious" events after we reach SS_Q6_DONE + * or SS_Q7_ERROR we don't want to destroy again so + * set ist_sm_busy again (shouldn't happen). + */ + if (ist->ist_sm_complete) { + ist->ist_sm_busy = B_TRUE; + (void) taskq_dispatch( + iscsit_global.global_dispatch_taskq, + sess_sm_complete, ist, DDI_SLEEP); + } + } +} + +static void +sess_sm_complete(void *ist_void) +{ + iscsit_sess_t *ist = ist_void; + + /* + * State machine has run to completion, destroy session + * + * If we have an associated STMF session we should clean it + * up now. + */ + mutex_enter(&ist->ist_mutex); + + ASSERT(ist->ist_conn_count == 0); + if (ist->ist_stmf_sess != NULL) { + stmf_deregister_scsi_session(ist->ist_lport, + ist->ist_stmf_sess); + kmem_free(ist->ist_stmf_sess->ss_rport_id, + sizeof (scsi_devid_desc_t) + + strlen(ist->ist_initiator_name) + 1); + stmf_free(ist->ist_stmf_sess); + } + mutex_exit(&ist->ist_mutex); + + iscsit_sess_destroy(ist); +} + +static void +sess_sm_event_dispatch(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + iscsit_conn_t *ict; + + /* State independent actions */ + switch (ctx->se_ctx_event) { + case SE_CONN_IN_LOGIN: + ict = ctx->se_event_data; + iscsit_sess_bind_conn(ist, ict); + break; + case SE_CONN_FAIL: + ict = ctx->se_event_data; + iscsit_sess_unbind_conn(ist, ict); + iscsit_conn_destroy_done(ict); + break; + } + + /* State dependent actions */ + switch (ist->ist_state) { + case SS_Q1_FREE: + sess_sm_q1_free(ist, ctx); + break; + case SS_Q2_ACTIVE: + sess_sm_q2_active(ist, ctx); + break; + case SS_Q3_LOGGED_IN: + sess_sm_q3_logged_in(ist, ctx); + break; + case SS_Q4_FAILED: + sess_sm_q4_failed(ist, ctx); + break; + case SS_Q5_CONTINUE: + sess_sm_q5_continue(ist, ctx); + break; + case SS_Q6_DONE: + sess_sm_q6_done(ist, ctx); + break; + case SS_Q7_ERROR: + sess_sm_q7_error(ist, ctx); + break; + default: + ASSERT(0); + break; + } + + kmem_free(ctx, sizeof (*ctx)); +} + +static void +sess_sm_q1_free(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + switch (ctx->se_ctx_event) { + case SE_CONN_IN_LOGIN: + /* N1 */ + sess_sm_new_state(ist, ctx, SS_Q2_ACTIVE); + break; + default: + ASSERT(0); + break; + } +} + + +static void +sess_sm_q2_active(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + switch (ctx->se_ctx_event) { + case SE_CONN_LOGGED_IN: + /* N2 track FFP connections */ + ist->ist_ffp_conn_count++; + sess_sm_new_state(ist, ctx, SS_Q3_LOGGED_IN); + break; + case SE_CONN_IN_LOGIN: + /* N2.1, don't care stay in this state */ + break; + case SE_CONN_FAIL: + /* N9 */ + sess_sm_new_state(ist, ctx, SS_Q7_ERROR); + break; + default: + ASSERT(0); + break; + } +} + +static void +sess_sm_q3_logged_in(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + iscsit_conn_t *ict; + + switch (ctx->se_ctx_event) { + case SE_CONN_IN_LOGIN: + case SE_CONN_FAIL: + /* N2.2, don't care */ + break; + case SE_CONN_LOGGED_IN: + /* N2.2, track FFP connections */ + ist->ist_ffp_conn_count++; + break; + case SE_CONN_FFP_FAIL: + /* + * Event data from event context is the associated connection + * which in this case happens to be the last FFP connection + * for the session. In certain cases we need to refer + * to this last valid connection (i.e. RFC3720 section 12.16) + * so we'll save off a pointer here for later use. + */ + ASSERT(ist->ist_ffp_conn_count >= 1); + ist->ist_failed_conn = (iscsit_conn_t *)ctx->se_event_data; + ist->ist_ffp_conn_count--; + if (ist->ist_ffp_conn_count == 0) { + /* N5 */ + sess_sm_new_state(ist, ctx, SS_Q4_FAILED); + } + break; + case SE_SESSION_CLOSE: + case SE_SESSION_REINSTATE: + /* N3 */ + mutex_enter(&ist->ist_mutex); + for (ict = list_head(&ist->ist_conn_list); + ict != NULL; + ict = list_next(&ist->ist_conn_list, ict)) { + idm_conn_event(ict->ict_ic, CE_LOGOUT_SESSION_SUCCESS, + NULL); + } + mutex_exit(&ist->ist_mutex); + + sess_sm_new_state(ist, ctx, SS_Q6_DONE); + break; + default: + ASSERT(0); + break; + } +} + +static void +sess_sm_timeout(void *arg) +{ + iscsit_sess_t *ist = arg; + + iscsit_sess_sm_event(ist, SE_SESSION_TIMEOUT, NULL); +} + +static void +sess_sm_q4_failed(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + /* Session timer must not be running when we leave this event */ + switch (ctx->se_ctx_event) { + case SE_CONN_IN_LOGIN: + /* N7 */ + sess_sm_new_state(ist, ctx, SS_Q5_CONTINUE); + break; + case SE_SESSION_REINSTATE: + /* N6 */ + untimeout(ist->ist_state_timeout); + /*FALLTHROUGH*/ + case SE_SESSION_TIMEOUT: + /* N6 */ + sess_sm_new_state(ist, ctx, SS_Q6_DONE); + break; + case SE_CONN_FAIL: + /* Don't care */ + break; + default: + ASSERT(0); + break; + } +} + +static void +sess_sm_q5_continue(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + switch (ctx->se_ctx_event) { + case SE_CONN_FAIL: + /* N5 */ + sess_sm_new_state(ist, ctx, SS_Q4_FAILED); + break; + case SE_CONN_LOGGED_IN: + /* N10 */ + sess_sm_new_state(ist, ctx, SS_Q3_LOGGED_IN); + break; + case SE_SESSION_REINSTATE: + /* N11 */ + sess_sm_new_state(ist, ctx, SS_Q6_DONE); + break; + default: + ASSERT(0); + break; + } +} + +static void +sess_sm_q6_done(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + /* Terminal state */ + switch (ctx->se_ctx_event) { + case SE_CONN_FAIL: + if (ist->ist_conn_count == 0) { + ist->ist_sm_complete = B_TRUE; + } + break; + default: + break; + } +} + +static void +sess_sm_q7_error(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + /* Terminal state */ + switch (ctx->se_ctx_event) { + case SE_CONN_FAIL: + if (ist->ist_conn_count == 0) { + ist->ist_sm_complete = B_TRUE; + } + break; + default: + break; + } +} + +static void +sess_sm_new_state(iscsit_sess_t *ist, sess_event_ctx_t *ctx, + iscsit_session_state_t new_state) +{ + int t2r_secs; + + /* + * Validate new state + */ + ASSERT(new_state != SS_UNDEFINED); + ASSERT3U(new_state, <, SS_MAX_STATE); + + new_state = (new_state < SS_MAX_STATE) ? + new_state : SS_UNDEFINED; + + cmn_err(CE_NOTE, "sess_sm_new_state: sess %p, evt %s(%d), " + "%s(%d) --> %s(%d)\n", + ist, iscsit_se_name[ctx->se_ctx_event], ctx->se_ctx_event, + iscsit_ss_name[ist->ist_state], ist->ist_state, + iscsit_ss_name[new_state], new_state); + DTRACE_PROBE3(sess__state__change, + iscsit_sess_t *, ist, sess_event_ctx_t *, ctx, + iscsit_session_state_t, new_state); + + mutex_enter(&ist->ist_mutex); + ist->ist_last_state = ist->ist_state; + ist->ist_state = new_state; + mutex_exit(&ist->ist_mutex); + + switch (ist->ist_state) { + case SS_Q1_FREE: + break; + case SS_Q2_ACTIVE: + iscsit_tgt_bind_sess(ist->ist_tgt, ist); + break; + case SS_Q3_LOGGED_IN: + break; + case SS_Q4_FAILED: + t2r_secs = + ist->ist_failed_conn->ict_op.op_default_time_2_retain; + ist->ist_state_timeout = timeout(sess_sm_timeout, ist, + drv_usectohz(t2r_secs*1000000)); + break; + case SS_Q5_CONTINUE: + break; + case SS_Q6_DONE: + case SS_Q7_ERROR: + iscsit_tgt_unbind_sess(ist->ist_tgt, ist); + if (ist->ist_conn_count == 0) { + ist->ist_sm_complete = B_TRUE; + } + break; + default: + ASSERT(0); + /*NOTREACHED*/ + } +}