#include <sys/cpuvar.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/socket.h>
#include <sys/strsubr.h>
#include <sys/note.h>
#include <sys/sdt.h>
#define IDM_CONN_SM_STRINGS
#define IDM_CN_NOTIFY_STRINGS
#include <sys/idm/idm.h>
boolean_t idm_sm_logging = B_FALSE;
extern idm_global_t idm;
static void
idm_conn_event_handler(void *event_ctx_opaque);
static void
idm_state_s1_free(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s2_xpt_wait(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s3_xpt_up(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s4_in_login(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s5_logged_in(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s6_in_logout(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_logout_req_timeout(void *arg);
static void
idm_state_s7_logout_req(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s8_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s9_init_error(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s9a_rejected(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s9b_wait_snd_done_cb(idm_pdu_t *pdu,
idm_status_t status);
static void
idm_state_s9b_wait_snd_done(idm_conn_t *ic,
idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s10_in_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s11_complete(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_state_s12_enable_dm(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_update_state(idm_conn_t *ic, idm_conn_state_t new_state,
idm_conn_event_ctx_t *event_ctx);
static void
idm_conn_unref(void *ic_void);
static void
idm_conn_reject_unref(void *ic_void);
static idm_pdu_event_action_t
idm_conn_sm_validate_pdu(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx,
idm_pdu_t *pdu);
static idm_status_t
idm_ffp_enable(idm_conn_t *ic);
static void
idm_ffp_disable(idm_conn_t *ic, idm_ffp_disable_t disable_type);
static void
idm_initial_login_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
static void
idm_login_success_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
idm_status_t
idm_conn_sm_init(idm_conn_t *ic)
{
char taskq_name[32];
ASSERT(ic->ic_internal_cid != 0);
(void) snprintf(taskq_name, sizeof (taskq_name) - 1, "conn_sm%08x",
ic->ic_internal_cid);
ic->ic_state_taskq = taskq_create(taskq_name, 1, minclsyspri, 4, 16384,
TASKQ_PREPOPULATE);
if (ic->ic_state_taskq == NULL) {
return (IDM_STATUS_FAIL);
}
idm_sm_audit_init(&ic->ic_state_audit);
mutex_init(&ic->ic_state_mutex, NULL, MUTEX_DEFAULT, NULL);
cv_init(&ic->ic_state_cv, NULL, CV_DEFAULT, NULL);
ic->ic_state = CS_S1_FREE;
ic->ic_last_state = CS_S1_FREE;
return (IDM_STATUS_SUCCESS);
}
void
idm_conn_sm_fini(idm_conn_t *ic)
{
if (ic->ic_state_taskq == NULL) {
return;
}
taskq_destroy(ic->ic_state_taskq);
cv_destroy(&ic->ic_state_cv);
mutex_enter(&ic->ic_state_mutex);
IDM_SM_TIMER_CLEAR(ic);
mutex_destroy(&ic->ic_state_mutex);
}
void
idm_conn_event(idm_conn_t *ic, idm_conn_event_t event, uintptr_t event_info)
{
mutex_enter(&ic->ic_state_mutex);
idm_conn_event_locked(ic, event, event_info, CT_NONE);
mutex_exit(&ic->ic_state_mutex);
}
idm_status_t
idm_conn_reinstate_event(idm_conn_t *old_ic, idm_conn_t *new_ic)
{
int result;
mutex_enter(&old_ic->ic_state_mutex);
if (((old_ic->ic_conn_type == CONN_TYPE_INI) &&
(old_ic->ic_state != CS_S8_CLEANUP)) ||
((old_ic->ic_conn_type == CONN_TYPE_TGT) &&
(old_ic->ic_state < CS_S5_LOGGED_IN))) {
result = IDM_STATUS_FAIL;
} else {
result = IDM_STATUS_SUCCESS;
new_ic->ic_reinstate_conn = old_ic;
idm_conn_event_locked(new_ic->ic_reinstate_conn,
CE_CONN_REINSTATE, (uintptr_t)new_ic, CT_NONE);
}
mutex_exit(&old_ic->ic_state_mutex);
return (result);
}
void
idm_conn_tx_pdu_event(idm_conn_t *ic, idm_conn_event_t event,
uintptr_t event_info)
{
ASSERT(mutex_owned(&ic->ic_state_mutex));
ic->ic_pdu_events++;
idm_conn_event_locked(ic, event, event_info, CT_TX_PDU);
}
void
idm_conn_rx_pdu_event(idm_conn_t *ic, idm_conn_event_t event,
uintptr_t event_info)
{
ASSERT(mutex_owned(&ic->ic_state_mutex));
ic->ic_pdu_events++;
idm_conn_event_locked(ic, event, event_info, CT_RX_PDU);
}
void
idm_conn_event_locked(idm_conn_t *ic, idm_conn_event_t event,
uintptr_t event_info, idm_pdu_event_type_t pdu_event_type)
{
idm_conn_event_ctx_t *event_ctx;
ASSERT(mutex_owned(&ic->ic_state_mutex));
idm_sm_audit_event(&ic->ic_state_audit, SAS_IDM_CONN,
(int)ic->ic_state, (int)event, event_info);
if ((ic->ic_state == CS_S9_INIT_ERROR) ||
(ic->ic_state == CS_S9A_REJECTED) ||
(ic->ic_state == CS_S11_COMPLETE)) {
if ((pdu_event_type == CT_TX_PDU) ||
(pdu_event_type == CT_RX_PDU)) {
ic->ic_pdu_events--;
idm_pdu_complete((idm_pdu_t *)event_info,
IDM_STATUS_SUCCESS);
}
IDM_SM_LOG(CE_NOTE, "*** Dropping event %s (%d) because of"
"state %s (%d)",
idm_ce_name[event], event,
idm_cs_name[ic->ic_state], ic->ic_state);
return;
}
idm_conn_hold(ic);
event_ctx = kmem_zalloc(sizeof (*event_ctx), KM_SLEEP);
event_ctx->iec_ic = ic;
event_ctx->iec_event = event;
event_ctx->iec_info = event_info;
event_ctx->iec_pdu_event_type = pdu_event_type;
(void) taskq_dispatch(ic->ic_state_taskq, &idm_conn_event_handler,
event_ctx, TQ_SLEEP);
}
static void
idm_conn_event_handler(void *event_ctx_opaque)
{
idm_conn_event_ctx_t *event_ctx = event_ctx_opaque;
idm_conn_t *ic = event_ctx->iec_ic;
idm_pdu_t *pdu = (idm_pdu_t *)event_ctx->iec_info;
idm_pdu_event_action_t action;
IDM_SM_LOG(CE_NOTE, "idm_conn_event_handler: conn %p event %s(%d)",
(void *)ic, idm_ce_name[event_ctx->iec_event],
event_ctx->iec_event);
DTRACE_PROBE2(conn__event,
idm_conn_t *, ic, idm_conn_event_ctx_t *, event_ctx);
ASSERT(event_ctx->iec_event != CE_UNDEFINED);
ASSERT3U(event_ctx->iec_event, <, CE_MAX_EVENT);
ASSERT(ic->ic_state != CS_S0_UNDEFINED);
ASSERT3U(ic->ic_state, <, CS_MAX_STATE);
event_ctx->iec_pdu_forwarded = B_FALSE;
if (event_ctx->iec_pdu_event_type != CT_NONE) {
ASSERT(pdu != NULL);
action = idm_conn_sm_validate_pdu(ic, event_ctx, pdu);
switch (action) {
case CA_TX_PROTOCOL_ERROR:
event_ctx->iec_event = CE_TX_PROTOCOL_ERROR;
break;
case CA_RX_PROTOCOL_ERROR:
event_ctx->iec_event = CE_RX_PROTOCOL_ERROR;
break;
case CA_FORWARD:
break;
case CA_DROP:
IDM_SM_LOG(CE_NOTE, "*** drop PDU %p", (void *) pdu);
idm_pdu_complete(pdu, IDM_STATUS_FAIL);
event_ctx->iec_info = (uintptr_t)NULL;
break;
default:
ASSERT(0);
break;
}
}
switch (ic->ic_state) {
case CS_S1_FREE:
idm_state_s1_free(ic, event_ctx);
break;
case CS_S2_XPT_WAIT:
idm_state_s2_xpt_wait(ic, event_ctx);
break;
case CS_S3_XPT_UP:
idm_state_s3_xpt_up(ic, event_ctx);
break;
case CS_S4_IN_LOGIN:
idm_state_s4_in_login(ic, event_ctx);
break;
case CS_S5_LOGGED_IN:
idm_state_s5_logged_in(ic, event_ctx);
break;
case CS_S6_IN_LOGOUT:
idm_state_s6_in_logout(ic, event_ctx);
break;
case CS_S7_LOGOUT_REQ:
idm_state_s7_logout_req(ic, event_ctx);
break;
case CS_S8_CLEANUP:
idm_state_s8_cleanup(ic, event_ctx);
break;
case CS_S9A_REJECTED:
idm_state_s9a_rejected(ic, event_ctx);
break;
case CS_S9B_WAIT_SND_DONE:
idm_state_s9b_wait_snd_done(ic, event_ctx);
break;
case CS_S9_INIT_ERROR:
idm_state_s9_init_error(ic, event_ctx);
break;
case CS_S10_IN_CLEANUP:
idm_state_s10_in_cleanup(ic, event_ctx);
break;
case CS_S11_COMPLETE:
idm_state_s11_complete(ic, event_ctx);
break;
case CS_S12_ENABLE_DM:
idm_state_s12_enable_dm(ic, event_ctx);
break;
default:
ASSERT(0);
break;
}
if (event_ctx->iec_pdu_event_type != CT_NONE) {
switch (action) {
case CA_TX_PROTOCOL_ERROR:
idm_pdu_tx_protocol_error(ic, pdu);
break;
case CA_RX_PROTOCOL_ERROR:
idm_pdu_rx_protocol_error(ic, pdu);
break;
case CA_FORWARD:
if (!event_ctx->iec_pdu_forwarded) {
if (event_ctx->iec_pdu_event_type ==
CT_RX_PDU) {
idm_pdu_rx_forward(ic, pdu);
} else {
idm_pdu_tx_forward(ic, pdu);
}
}
break;
case CA_DROP:
ASSERT3P(event_ctx->iec_info, ==, NULL);
break;
default:
ASSERT(0);
break;
}
}
if ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ||
(event_ctx->iec_pdu_event_type == CT_RX_PDU)) {
mutex_enter(&ic->ic_state_mutex);
ic->ic_pdu_events--;
mutex_exit(&ic->ic_state_mutex);
}
idm_conn_rele(ic);
kmem_free(event_ctx, sizeof (*event_ctx));
}
static void
idm_state_s1_free(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
switch (event_ctx->iec_event) {
case CE_CONNECT_REQ:
idm_update_state(ic, CS_S2_XPT_WAIT, event_ctx);
break;
case CE_CONNECT_ACCEPT:
idm_update_state(ic, CS_S3_XPT_UP, event_ctx);
break;
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
break;
default:
ASSERT(0);
}
}
static void
idm_state_s2_xpt_wait(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
switch (event_ctx->iec_event) {
case CE_CONNECT_SUCCESS:
idm_update_state(ic, CS_S4_IN_LOGIN, event_ctx);
break;
case CE_TRANSPORT_FAIL:
case CE_CONNECT_FAIL:
case CE_LOGOUT_OTHER_CONN_RCV:
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
break;
default:
ASSERT(0);
}
}
static void
idm_login_timeout(void *arg)
{
idm_conn_t *ic = arg;
ic->ic_state_timeout = 0;
idm_conn_event(ic, CE_LOGIN_TIMEOUT, (uintptr_t)NULL);
}
static void
idm_state_s3_xpt_up(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
switch (event_ctx->iec_event) {
case CE_LOGIN_RCV:
idm_initial_login_actions(ic, event_ctx);
idm_update_state(ic, CS_S4_IN_LOGIN, event_ctx);
break;
case CE_LOGIN_TIMEOUT:
(void) idm_notify_client(ic, CN_LOGIN_FAIL, (uintptr_t)NULL);
idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
break;
case CE_CONNECT_REJECT:
IDM_SM_TIMER_CLEAR(ic);
idm_update_state(ic, CS_S9A_REJECTED, event_ctx);
break;
case CE_CONNECT_FAIL:
case CE_TRANSPORT_FAIL:
case CE_LOGOUT_OTHER_CONN_SND:
IDM_SM_TIMER_CLEAR(ic);
(void) idm_notify_client(ic, CN_LOGIN_FAIL, (uintptr_t)NULL);
idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
break;
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
break;
default:
ASSERT(0);
}
}
static void
idm_state_s4_in_login(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
idm_pdu_t *pdu;
switch (event_ctx->iec_event) {
case CE_LOGIN_SUCCESS_RCV:
case CE_LOGIN_SUCCESS_SND:
ASSERT(ic->ic_client_callback == NULL);
IDM_SM_TIMER_CLEAR(ic);
idm_login_success_actions(ic, event_ctx);
if (ic->ic_rdma_extensions) {
idm_update_state(ic, CS_S12_ENABLE_DM, event_ctx);
} else {
idm_update_state(ic, CS_S5_LOGGED_IN, event_ctx);
}
break;
case CE_LOGIN_TIMEOUT:
(void) idm_notify_client(ic, CN_LOGIN_FAIL, (uintptr_t)NULL);
idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
break;
case CE_LOGIN_FAIL_SND:
IDM_SM_TIMER_CLEAR(ic);
pdu = (idm_pdu_t *)event_ctx->iec_info;
ASSERT(ic->ic_client_callback == NULL);
ic->ic_client_callback = pdu->isp_callback;
pdu->isp_callback =
idm_state_s9b_wait_snd_done_cb;
idm_update_state(ic, CS_S9B_WAIT_SND_DONE,
event_ctx);
break;
case CE_LOGIN_FAIL_RCV:
ASSERT(ic->ic_client_callback == NULL);
event_ctx->iec_pdu_forwarded = B_TRUE;
pdu = (idm_pdu_t *)event_ctx->iec_info;
idm_pdu_rx_forward(ic, pdu);
case CE_TRANSPORT_FAIL:
case CE_LOGOUT_OTHER_CONN_SND:
case CE_LOGOUT_OTHER_CONN_RCV:
IDM_SM_TIMER_CLEAR(ic);
(void) idm_notify_client(ic, CN_LOGIN_FAIL, (uintptr_t)NULL);
idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
break;
case CE_LOGOUT_SESSION_SUCCESS:
IDM_SM_TIMER_CLEAR(ic);
if (IDM_CONN_ISTGT(ic)) {
ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
} else {
ic->ic_transport_ops->it_ini_conn_disconnect(ic);
}
idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
break;
case CE_LOGIN_SND:
ASSERT(ic->ic_client_callback == NULL);
mutex_enter(&ic->ic_state_mutex);
if (!(ic->ic_state_flags & CF_INITIAL_LOGIN)) {
idm_initial_login_actions(ic, event_ctx);
}
mutex_exit(&ic->ic_state_mutex);
break;
case CE_MISC_TX:
case CE_MISC_RX:
case CE_LOGIN_RCV:
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
break;
default:
ASSERT(0);
}
}
static void
idm_state_s5_logged_in(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
switch (event_ctx->iec_event) {
case CE_MISC_RX:
case CE_LOGOUT_THIS_CONN_RCV:
case CE_LOGOUT_THIS_CONN_SND:
case CE_LOGOUT_OTHER_CONN_RCV:
case CE_LOGOUT_OTHER_CONN_SND:
idm_ffp_disable(ic, FD_CONN_LOGOUT);
idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx);
break;
case CE_LOGOUT_SESSION_RCV:
case CE_LOGOUT_SESSION_SND:
idm_ffp_disable(ic, FD_SESS_LOGOUT);
idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx);
break;
case CE_LOGOUT_SESSION_SUCCESS:
idm_ffp_disable(ic, FD_SESS_LOGOUT);
if (IDM_CONN_ISTGT(ic)) {
ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
} else {
ic->ic_transport_ops->it_ini_conn_disconnect(ic);
}
idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
break;
case CE_ASYNC_LOGOUT_RCV:
case CE_ASYNC_LOGOUT_SND:
idm_update_state(ic, CS_S7_LOGOUT_REQ, event_ctx);
break;
case CE_TRANSPORT_FAIL:
case CE_ASYNC_DROP_CONN_RCV:
case CE_ASYNC_DROP_CONN_SND:
case CE_ASYNC_DROP_ALL_CONN_RCV:
case CE_ASYNC_DROP_ALL_CONN_SND:
idm_ffp_disable(ic, FD_CONN_FAIL);
idm_update_state(ic, CS_S8_CLEANUP, event_ctx);
break;
case CE_MISC_TX:
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
case CE_LOGIN_TIMEOUT:
break;
default:
ASSERT(0);
}
}
static void
idm_state_s6_in_logout_success_snd_done(idm_pdu_t *pdu, idm_status_t status)
{
idm_conn_t *ic = pdu->isp_ic;
pdu->isp_status = status;
idm_conn_event(ic, CE_LOGOUT_SUCCESS_SND_DONE, (uintptr_t)pdu);
}
static void
idm_state_s6_in_logout_fail_snd_done(idm_pdu_t *pdu, idm_status_t status)
{
idm_conn_t *ic = pdu->isp_ic;
pdu->isp_status = status;
idm_conn_event(ic, CE_LOGOUT_FAIL_SND_DONE, (uintptr_t)pdu);
}
static void
idm_state_s6_in_logout(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
idm_pdu_t *pdu;
switch (event_ctx->iec_event) {
case CE_LOGOUT_SUCCESS_SND_DONE:
pdu = (idm_pdu_t *)event_ctx->iec_info;
ASSERT(IDM_CONN_ISTGT(ic));
ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
pdu->isp_callback = ic->ic_client_callback;
ic->ic_client_callback = NULL;
idm_pdu_complete(pdu, pdu->isp_status);
idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
break;
case CE_LOGOUT_FAIL_SND_DONE:
pdu = (idm_pdu_t *)event_ctx->iec_info;
pdu->isp_callback = ic->ic_client_callback;
ic->ic_client_callback = NULL;
idm_pdu_complete(pdu, pdu->isp_status);
idm_update_state(ic, CS_S8_CLEANUP, event_ctx);
break;
case CE_LOGOUT_SUCCESS_SND:
case CE_LOGOUT_FAIL_SND:
pdu = (idm_pdu_t *)event_ctx->iec_info;
ASSERT(ic->ic_client_callback == NULL);
ic->ic_client_callback = pdu->isp_callback;
if (event_ctx->iec_event == CE_LOGOUT_SUCCESS_SND) {
pdu->isp_callback =
idm_state_s6_in_logout_success_snd_done;
} else {
pdu->isp_callback =
idm_state_s6_in_logout_fail_snd_done;
}
break;
case CE_LOGOUT_SUCCESS_RCV:
event_ctx->iec_pdu_forwarded = B_TRUE;
pdu = (idm_pdu_t *)event_ctx->iec_info;
idm_pdu_rx_forward(ic, pdu);
case CE_LOGOUT_SESSION_SUCCESS:
if (IDM_CONN_ISTGT(ic)) {
ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
} else {
ic->ic_transport_ops->it_ini_conn_disconnect(ic);
}
idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
break;
case CE_ASYNC_LOGOUT_RCV:
break;
case CE_TRANSPORT_FAIL:
case CE_ASYNC_DROP_CONN_RCV:
case CE_ASYNC_DROP_CONN_SND:
case CE_ASYNC_DROP_ALL_CONN_RCV:
case CE_ASYNC_DROP_ALL_CONN_SND:
case CE_LOGOUT_FAIL_RCV:
idm_update_state(ic, CS_S8_CLEANUP, event_ctx);
break;
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
case CE_MISC_TX:
case CE_MISC_RX:
case CE_LOGIN_TIMEOUT:
break;
default:
ASSERT(0);
}
}
static void
idm_logout_req_timeout(void *arg)
{
idm_conn_t *ic = arg;
ic->ic_state_timeout = 0;
idm_conn_event(ic, CE_LOGOUT_TIMEOUT, (uintptr_t)NULL);
}
static void
idm_state_s7_logout_req(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
switch (event_ctx->iec_event) {
case CE_LOGOUT_THIS_CONN_RCV:
case CE_LOGOUT_THIS_CONN_SND:
case CE_LOGOUT_OTHER_CONN_RCV:
case CE_LOGOUT_OTHER_CONN_SND:
if (IDM_CONN_ISTGT(ic)) {
IDM_SM_TIMER_CLEAR(ic);
}
idm_ffp_disable(ic, FD_CONN_LOGOUT);
idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx);
break;
case CE_LOGOUT_SESSION_RCV:
case CE_LOGOUT_SESSION_SND:
if (IDM_CONN_ISTGT(ic)) {
IDM_SM_TIMER_CLEAR(ic);
}
idm_ffp_disable(ic, FD_SESS_LOGOUT);
idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx);
break;
case CE_ASYNC_LOGOUT_RCV:
case CE_ASYNC_LOGOUT_SND:
break;
case CE_TRANSPORT_FAIL:
case CE_ASYNC_DROP_CONN_RCV:
case CE_ASYNC_DROP_CONN_SND:
case CE_ASYNC_DROP_ALL_CONN_RCV:
case CE_ASYNC_DROP_ALL_CONN_SND:
if (IDM_CONN_ISTGT(ic)) {
IDM_SM_TIMER_CLEAR(ic);
}
case CE_LOGOUT_TIMEOUT:
idm_ffp_disable(ic, FD_CONN_FAIL);
idm_update_state(ic, CS_S8_CLEANUP, event_ctx);
break;
case CE_LOGOUT_SESSION_SUCCESS:
if (IDM_CONN_ISTGT(ic)) {
IDM_SM_TIMER_CLEAR(ic);
}
idm_ffp_disable(ic, FD_SESS_LOGOUT);
if (IDM_CONN_ISTGT(ic)) {
ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
} else {
ic->ic_transport_ops->it_ini_conn_disconnect(ic);
}
idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
break;
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
case CE_MISC_TX:
case CE_MISC_RX:
case CE_LOGIN_TIMEOUT:
break;
default:
ASSERT(0);
}
}
static void
idm_cleanup_timeout(void *arg)
{
idm_conn_t *ic = arg;
ic->ic_state_timeout = 0;
idm_conn_event(ic, CE_CLEANUP_TIMEOUT, (uintptr_t)NULL);
}
static void
idm_state_s8_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
idm_pdu_t *pdu;
switch (event_ctx->iec_event) {
case CE_LOGOUT_SUCCESS_RCV:
case CE_LOGOUT_SUCCESS_SND:
case CE_LOGOUT_SESSION_SUCCESS:
IDM_SM_TIMER_CLEAR(ic);
case CE_CLEANUP_TIMEOUT:
idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
break;
case CE_LOGOUT_OTHER_CONN_RCV:
case CE_LOGOUT_OTHER_CONN_SND:
idm_update_state(ic, CS_S10_IN_CLEANUP, event_ctx);
break;
case CE_LOGOUT_SUCCESS_SND_DONE:
case CE_LOGOUT_FAIL_SND_DONE:
pdu = (idm_pdu_t *)event_ctx->iec_info;
pdu->isp_callback = ic->ic_client_callback;
ic->ic_client_callback = NULL;
idm_pdu_complete(pdu, pdu->isp_status);
break;
case CE_LOGOUT_SESSION_RCV:
case CE_LOGOUT_SESSION_SND:
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
case CE_MISC_TX:
case CE_MISC_RX:
case CE_TRANSPORT_FAIL:
case CE_LOGIN_TIMEOUT:
case CE_LOGOUT_TIMEOUT:
break;
default:
ASSERT(0);
}
}
static void
idm_state_s9_init_error(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
}
static void
idm_state_s9a_rejected(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
}
static void
idm_state_s9b_wait_snd_done_cb(idm_pdu_t *pdu, idm_status_t status)
{
idm_conn_t *ic = pdu->isp_ic;
pdu->isp_status = status;
idm_conn_event(ic, CE_LOGIN_FAIL_SND_DONE, (uintptr_t)pdu);
}
static void
idm_state_s9b_wait_snd_done(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
idm_pdu_t *pdu;
switch (event_ctx->iec_event) {
case CE_LOGIN_FAIL_SND_DONE:
pdu = (idm_pdu_t *)event_ctx->iec_info;
pdu->isp_callback = ic->ic_client_callback;
ic->ic_client_callback = NULL;
idm_pdu_complete(pdu, pdu->isp_status);
idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
break;
}
}
static void
idm_state_s10_in_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
idm_pdu_t *pdu;
switch (event_ctx->iec_event) {
case CE_LOGOUT_FAIL_RCV:
case CE_LOGOUT_FAIL_SND:
idm_update_state(ic, CS_S8_CLEANUP, event_ctx);
break;
case CE_LOGOUT_SUCCESS_SND:
case CE_LOGOUT_SUCCESS_RCV:
case CE_LOGOUT_SESSION_SUCCESS:
IDM_SM_TIMER_CLEAR(ic);
case CE_CLEANUP_TIMEOUT:
idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
break;
case CE_LOGOUT_SUCCESS_SND_DONE:
case CE_LOGOUT_FAIL_SND_DONE:
pdu = (idm_pdu_t *)event_ctx->iec_info;
pdu->isp_callback = ic->ic_client_callback;
ic->ic_client_callback = NULL;
idm_pdu_complete(pdu, pdu->isp_status);
break;
case CE_TX_PROTOCOL_ERROR:
case CE_RX_PROTOCOL_ERROR:
case CE_MISC_TX:
case CE_MISC_RX:
case CE_LOGIN_TIMEOUT:
case CE_LOGOUT_TIMEOUT:
break;
default:
ASSERT(0);
}
}
static void
idm_state_s11_complete(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
idm_pdu_t *pdu;
switch (event_ctx->iec_event) {
case CE_LOGOUT_SUCCESS_SND_DONE:
case CE_LOGOUT_FAIL_SND_DONE:
pdu = (idm_pdu_t *)event_ctx->iec_info;
pdu->isp_callback = ic->ic_client_callback;
ic->ic_client_callback = NULL;
idm_pdu_complete(pdu, pdu->isp_status);
break;
}
}
static void
idm_state_s12_enable_dm(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
switch (event_ctx->iec_event) {
case CE_ENABLE_DM_SUCCESS:
idm_update_state(ic, CS_S5_LOGGED_IN, event_ctx);
break;
case CE_ENABLE_DM_FAIL:
idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
break;
case CE_TRANSPORT_FAIL:
break;
default:
ASSERT(0);
break;
}
}
static void
idm_update_state(idm_conn_t *ic, idm_conn_state_t new_state,
idm_conn_event_ctx_t *event_ctx)
{
int rc;
idm_status_t idm_status;
ASSERT(new_state != CS_S0_UNDEFINED);
ASSERT3U(new_state, <, CS_MAX_STATE);
new_state = (new_state < CS_MAX_STATE) ?
new_state : CS_S0_UNDEFINED;
IDM_SM_LOG(CE_NOTE, "idm_update_state: conn %p, evt %s(%d), "
"%s(%d) --> %s(%d)", (void *)ic,
idm_ce_name[event_ctx->iec_event], event_ctx->iec_event,
idm_cs_name[ic->ic_state], ic->ic_state,
idm_cs_name[new_state], new_state);
DTRACE_PROBE2(conn__state__change,
idm_conn_t *, ic, idm_conn_state_t, new_state);
mutex_enter(&ic->ic_state_mutex);
idm_sm_audit_state_change(&ic->ic_state_audit, SAS_IDM_CONN,
(int)ic->ic_state, (int)new_state);
ic->ic_last_state = ic->ic_state;
ic->ic_state = new_state;
cv_signal(&ic->ic_state_cv);
mutex_exit(&ic->ic_state_mutex);
switch (ic->ic_state) {
case CS_S1_FREE:
ASSERT(0);
break;
case CS_S2_XPT_WAIT:
if ((rc = idm_ini_conn_finish(ic)) != 0) {
idm_conn_event(ic, CE_CONNECT_FAIL, (uintptr_t)NULL);
} else {
idm_conn_event(ic, CE_CONNECT_SUCCESS, (uintptr_t)NULL);
}
break;
case CS_S3_XPT_UP:
if ((rc = idm_tgt_conn_finish(ic)) != IDM_STATUS_SUCCESS) {
switch (rc) {
case IDM_STATUS_REJECT:
idm_conn_event(ic, CE_CONNECT_REJECT,
(uintptr_t)NULL);
break;
default:
idm_conn_event(ic, CE_CONNECT_FAIL,
(uintptr_t)NULL);
break;
}
}
IDM_SM_TIMER_CHECK(ic);
ic->ic_state_timeout = timeout(idm_login_timeout, ic,
drv_usectohz(IDM_LOGIN_SECONDS * 1000000));
break;
case CS_S4_IN_LOGIN:
if (ic->ic_conn_type == CONN_TYPE_INI) {
(void) idm_notify_client(ic, CN_READY_FOR_LOGIN,
(uintptr_t)NULL);
mutex_enter(&ic->ic_state_mutex);
ic->ic_state_flags |= CF_LOGIN_READY;
cv_signal(&ic->ic_state_cv);
mutex_exit(&ic->ic_state_mutex);
}
break;
case CS_S5_LOGGED_IN:
ASSERT(!ic->ic_ffp);
idm_status = idm_ffp_enable(ic);
if (idm_status != IDM_STATUS_SUCCESS) {
idm_conn_event(ic, CE_TRANSPORT_FAIL, (uintptr_t)NULL);
}
if (ic->ic_reinstate_conn) {
idm_conn_event(ic->ic_reinstate_conn,
CE_CONN_REINSTATE_SUCCESS, (uintptr_t)NULL);
}
break;
case CS_S6_IN_LOGOUT:
break;
case CS_S7_LOGOUT_REQ:
if (IDM_CONN_ISTGT(ic)) {
IDM_SM_TIMER_CHECK(ic);
ic->ic_state_timeout = timeout(idm_logout_req_timeout,
ic, drv_usectohz(IDM_LOGOUT_SECONDS*1000000));
}
break;
case CS_S8_CLEANUP:
if (IDM_CONN_ISTGT(ic)) {
ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
} else {
ic->ic_transport_ops->it_ini_conn_disconnect(ic);
}
(void) idm_task_abort(ic, NULL, AT_INTERNAL_SUSPEND);
IDM_SM_TIMER_CHECK(ic);
ic->ic_state_timeout = timeout(idm_cleanup_timeout, ic,
drv_usectohz(IDM_CLEANUP_SECONDS*1000000));
break;
case CS_S10_IN_CLEANUP:
break;
case CS_S9A_REJECTED:
idm_refcnt_async_wait_ref(&ic->ic_refcnt,
&idm_conn_reject_unref);
break;
case CS_S9B_WAIT_SND_DONE:
break;
case CS_S9_INIT_ERROR:
if (IDM_CONN_ISTGT(ic)) {
ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
} else {
mutex_enter(&ic->ic_state_mutex);
ic->ic_state_flags |= CF_ERROR;
ic->ic_conn_sm_status = IDM_STATUS_FAIL;
cv_signal(&ic->ic_state_cv);
mutex_exit(&ic->ic_state_mutex);
if (ic->ic_last_state != CS_S1_FREE &&
ic->ic_last_state != CS_S2_XPT_WAIT) {
ic->ic_transport_ops->it_ini_conn_disconnect(
ic);
} else {
(void) idm_notify_client(ic, CN_CONNECT_FAIL,
(uintptr_t)NULL);
}
}
case CS_S11_COMPLETE:
if (IDM_CONN_ISTGT(ic) ||
((ic->ic_last_state != CS_S1_FREE) &&
(ic->ic_last_state != CS_S2_XPT_WAIT))) {
(void) idm_notify_client(ic, CN_CONNECT_LOST,
(uintptr_t)(ic->ic_last_state == CS_S4_IN_LOGIN));
}
(void) idm_task_abort(ic, NULL, AT_INTERNAL_ABORT);
idm_refcnt_async_wait_ref(&ic->ic_refcnt, &idm_conn_unref);
break;
case CS_S12_ENABLE_DM:
idm_status = (IDM_CONN_ISINI(ic)) ?
ic->ic_transport_ops->it_ini_enable_datamover(ic) :
ic->ic_transport_ops->it_tgt_enable_datamover(ic);
if (idm_status == IDM_STATUS_SUCCESS) {
idm_conn_event(ic, CE_ENABLE_DM_SUCCESS,
(uintptr_t)NULL);
} else {
idm_conn_event(ic, CE_ENABLE_DM_FAIL, (uintptr_t)NULL);
}
break;
default:
ASSERT(0);
break;
}
}
static void
idm_conn_unref(void *ic_void)
{
idm_conn_t *ic = ic_void;
if (IDM_CONN_ISTGT(ic)) {
(void) idm_notify_client(ic, CN_CONNECT_DESTROY,
(uintptr_t)NULL);
idm_svc_conn_destroy(ic);
} else {
(void) idm_notify_client(ic, CN_CONNECT_DESTROY,
(uintptr_t)NULL);
}
}
static void
idm_conn_reject_unref(void *ic_void)
{
idm_conn_t *ic = ic_void;
ASSERT(IDM_CONN_ISTGT(ic));
idm_svc_conn_destroy(ic);
}
static idm_pdu_event_action_t
idm_conn_sm_validate_pdu(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx,
idm_pdu_t *pdu)
{
char *reason_string;
idm_pdu_event_action_t action;
ASSERT((event_ctx->iec_pdu_event_type == CT_RX_PDU) ||
(event_ctx->iec_pdu_event_type == CT_TX_PDU));
switch (IDM_PDU_OPCODE(pdu)) {
case ISCSI_OP_NOOP_OUT:
case ISCSI_OP_SCSI_CMD:
case ISCSI_OP_SCSI_TASK_MGT_MSG:
case ISCSI_OP_LOGIN_CMD:
case ISCSI_OP_TEXT_CMD:
case ISCSI_OP_SCSI_DATA:
case ISCSI_OP_LOGOUT_CMD:
case ISCSI_OP_SNACK_CMD:
if (IDM_CONN_ISINI(ic) &&
(event_ctx->iec_pdu_event_type == CT_RX_PDU)) {
reason_string = "Invalid RX PDU for initiator";
action = CA_RX_PROTOCOL_ERROR;
goto validate_pdu_done;
}
if (IDM_CONN_ISTGT(ic) &&
(event_ctx->iec_pdu_event_type == CT_TX_PDU)) {
reason_string = "Invalid TX PDU for target";
action = CA_TX_PROTOCOL_ERROR;
goto validate_pdu_done;
}
break;
case ISCSI_OP_NOOP_IN:
case ISCSI_OP_SCSI_RSP:
case ISCSI_OP_SCSI_TASK_MGT_RSP:
case ISCSI_OP_LOGIN_RSP:
case ISCSI_OP_TEXT_RSP:
case ISCSI_OP_SCSI_DATA_RSP:
case ISCSI_OP_LOGOUT_RSP:
case ISCSI_OP_RTT_RSP:
case ISCSI_OP_ASYNC_EVENT:
case ISCSI_OP_REJECT_MSG:
if (IDM_CONN_ISTGT(ic) &&
(event_ctx->iec_pdu_event_type == CT_RX_PDU)) {
reason_string = "Invalid RX PDU for target";
action = CA_RX_PROTOCOL_ERROR;
goto validate_pdu_done;
}
if (IDM_CONN_ISINI(ic) &&
(event_ctx->iec_pdu_event_type == CT_TX_PDU)) {
reason_string = "Invalid TX PDU for initiator";
action = CA_TX_PROTOCOL_ERROR;
goto validate_pdu_done;
}
break;
default:
reason_string = "Unknown PDU Type";
action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
goto validate_pdu_done;
}
reason_string = "PDU not allowed in current state";
switch (IDM_PDU_OPCODE(pdu)) {
case ISCSI_OP_NOOP_OUT:
case ISCSI_OP_NOOP_IN:
switch (ic->ic_state) {
case CS_S4_IN_LOGIN:
case CS_S5_LOGGED_IN:
case CS_S6_IN_LOGOUT:
case CS_S7_LOGOUT_REQ:
action = CA_FORWARD;
goto validate_pdu_done;
case CS_S8_CLEANUP:
case CS_S10_IN_CLEANUP:
action = CA_DROP;
goto validate_pdu_done;
default:
action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
goto validate_pdu_done;
}
case ISCSI_OP_SCSI_CMD:
case ISCSI_OP_SCSI_RSP:
case ISCSI_OP_SCSI_TASK_MGT_MSG:
case ISCSI_OP_SCSI_TASK_MGT_RSP:
case ISCSI_OP_SCSI_DATA:
case ISCSI_OP_SCSI_DATA_RSP:
case ISCSI_OP_RTT_RSP:
case ISCSI_OP_SNACK_CMD:
case ISCSI_OP_TEXT_CMD:
case ISCSI_OP_TEXT_RSP:
switch (ic->ic_state) {
case CS_S5_LOGGED_IN:
case CS_S6_IN_LOGOUT:
case CS_S7_LOGOUT_REQ:
action = CA_FORWARD;
goto validate_pdu_done;
case CS_S8_CLEANUP:
case CS_S10_IN_CLEANUP:
action = CA_DROP;
goto validate_pdu_done;
default:
action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
goto validate_pdu_done;
}
case ISCSI_OP_LOGOUT_CMD:
case ISCSI_OP_LOGOUT_RSP:
case ISCSI_OP_REJECT_MSG:
case ISCSI_OP_ASYNC_EVENT:
switch (ic->ic_state) {
case CS_S5_LOGGED_IN:
case CS_S6_IN_LOGOUT:
case CS_S7_LOGOUT_REQ:
action = CA_FORWARD;
goto validate_pdu_done;
case CS_S8_CLEANUP:
case CS_S10_IN_CLEANUP:
action = CA_DROP;
goto validate_pdu_done;
default:
action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
goto validate_pdu_done;
}
case ISCSI_OP_LOGIN_CMD:
case ISCSI_OP_LOGIN_RSP:
switch (ic->ic_state) {
case CS_S3_XPT_UP:
case CS_S4_IN_LOGIN:
action = CA_FORWARD;
goto validate_pdu_done;
default:
action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
goto validate_pdu_done;
}
default:
ASSERT(0);
}
action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
validate_pdu_done:
if (action != CA_FORWARD) {
DTRACE_PROBE2(idm__int__protocol__error,
idm_conn_event_ctx_t *, event_ctx,
char *, reason_string);
}
return (action);
}
void
idm_pdu_tx_protocol_error(idm_conn_t *ic, idm_pdu_t *pdu)
{
idm_pdu_complete(pdu, IDM_STATUS_PROTOCOL_ERROR);
}
void
idm_pdu_rx_protocol_error(idm_conn_t *ic, idm_pdu_t *pdu)
{
(*ic->ic_conn_ops.icb_rx_error)(ic, pdu, IDM_STATUS_PROTOCOL_ERROR);
}
idm_status_t
idm_notify_client(idm_conn_t *ic, idm_client_notify_t cn, uintptr_t data)
{
ASSERT(!mutex_owned(&ic->ic_state_mutex));
cn = (cn > CN_MAX) ? CN_MAX : cn;
IDM_SM_LOG(CE_NOTE, "idm_notify_client: ic=%p %s(%d)\n",
(void *)ic, idm_cn_strings[cn], cn);
return ((*ic->ic_conn_ops.icb_client_notify)(ic, cn, data));
}
static idm_status_t
idm_ffp_enable(idm_conn_t *ic)
{
idm_status_t rc;
mutex_enter(&ic->ic_state_mutex);
ic->ic_ffp = B_TRUE;
mutex_exit(&ic->ic_state_mutex);
rc = idm_notify_client(ic, CN_FFP_ENABLED, (uintptr_t)NULL);
if (rc != IDM_STATUS_SUCCESS) {
mutex_enter(&ic->ic_state_mutex);
ic->ic_ffp = B_FALSE;
mutex_exit(&ic->ic_state_mutex);
}
return (rc);
}
static void
idm_ffp_disable(idm_conn_t *ic, idm_ffp_disable_t disable_type)
{
mutex_enter(&ic->ic_state_mutex);
ic->ic_ffp = B_FALSE;
mutex_exit(&ic->ic_state_mutex);
(void) idm_notify_client(ic, CN_FFP_DISABLED,
(uintptr_t)disable_type);
}
static void
idm_initial_login_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
ASSERT((event_ctx->iec_event == CE_LOGIN_RCV) ||
(event_ctx->iec_event == CE_LOGIN_SND));
ic->ic_state_flags |= CF_INITIAL_LOGIN;
}
static void
idm_login_success_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
{
idm_pdu_t *pdu = (idm_pdu_t *)event_ctx->iec_info;
iscsi_login_hdr_t *login_req =
(iscsi_login_hdr_t *)pdu->isp_hdr;
ASSERT((event_ctx->iec_event == CE_LOGIN_SUCCESS_RCV) ||
(event_ctx->iec_event == CE_LOGIN_SUCCESS_SND));
mutex_enter(&ic->ic_state_mutex);
ic->ic_login_cid = ntohs(login_req->cid);
ic->ic_login_info_valid = B_TRUE;
mutex_exit(&ic->ic_state_mutex);
}