/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * iSCSI command interfaces */ #include "iscsi.h" /* internal interfaces */ static void iscsi_cmd_state_free(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg); static void iscsi_cmd_state_pending(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg); static void iscsi_cmd_state_active(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg); static void iscsi_cmd_state_aborting(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg); static void iscsi_cmd_state_idm_aborting(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg); static void iscsi_cmd_state_completed(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg); static char *iscsi_cmd_state_str(iscsi_cmd_state_t state); static char *iscsi_cmd_event_str(iscsi_cmd_event_t event); /* LINTED E_STATIC_UNUSED */ static char *iscsi_cmd_type_str(iscsi_cmd_type_t type); #define ISCSI_INTERNAL_CMD_TIMEOUT 60 #define ISCSI_CMD_ISSUE_CALLBACK(icmdp, status) \ icmdp->cmd_completed = B_TRUE; \ icmdp->cmd_result = status; \ cv_broadcast(&icmdp->cmd_completion); #define ISCSI_CMD_SET_REASON_STAT(icmdp, reason, stat) \ icmdp->cmd_un.scsi.pkt->pkt_reason = reason; \ icmdp->cmd_un.scsi.pkt->pkt_statistics = stat; /* * The following private tunable, settable via * set iscsi:iscsi_cmd_timeout_factor = 2 * in /etc/system, provides customer relief for configurations experiencing * SCSI command timeouts due to high-latency/high-loss network connections * or slow target response (possibly due to backing store issues). If frequent * use of this tunable is necessary, a beter mechanism must be provided. */ int iscsi_cmd_timeout_factor = 1; /* * +--------------------------------------------------------------------+ * | External Command Interfaces | * +--------------------------------------------------------------------+ */ /* * iscsi_cmd_state_machine - This function is used to drive the * state machine of the internal iscsi commands. It takes in a command * and the associated event affecting the command. * * 7.1.3 Command State Diagram for an Initiator * Symbolic Names for States: * C1: FREE - State on instantiation, or after successful * completion. * C2: PENDING - Command is in the session's pending queue awaiting * its turn to be sent on the wire. * C3: ACTIVE - Command has been sent on the wire and is * awaiting completion. * C4: ABORTING - Command which was sent on the wire has not timed * out or been requested to abort by an upper layer * driver. At this point there is a task management * command in the active queue trying to abort the task. * C4': IDM ABORTING - SCSI command is owned by IDM and idm_task_abort * has been called for this command. * C5: COMPLETED - Command which is ready to complete via pkt callback. * * The state diagram is as follows: * ------- * / C1 \ * I-------->\ /<------------ * N| ---+--- | * T| |E1 | * E| V | * R| ------- | * N+--------/ C2 \ | * A| E4/6/7\ /-------- | * L| ---+--- E4/6/7| | * | |E2 E10 | | * C| V | S | * M| _______ | C | * D+--------/ C3 \ | S | * S E3/4/6/7\ /-------+ I | * /---+---E3/4/6/7| | * / | E9/10| | * ------/ E4/6| | C | * | V | M | * E7| ------- | D | * SCSI| - >/ C4 \ | S | * | / \ /-------+ | * | | ---+---E3/6/7/9| | * | | E4| | V /E8 * | ------ | ------- * +-\ / / C5 \ * V \-------/ /---->\ / * ------- E7 / ---+--- * / C4' \ / * \ /------/ E9 * ------- * * The state transition table is as follows: * * +---------+---+---+-----+----+--------------+ * |C1 |C2 |C3 |C4 |C4' |C5 | * ---+---------+---+---+-----+----+--------------+ * C1| - |E1 | - | - | - | | * ---+---------+---+---+-----+----+--------------+ * C2|E4/6/7 |- |E2 | - | - |E4/6/7/10 | * ---+---------+---+---+-----+----+--------------+ * C3|E3/4/6/7 |- |- |E4/6 |E7 |E3/4/6/7/9/10 | * ---+---------+---+---+-----+----+--------------+ * C4| |- |- |E4 |E7 |E3/6/7/9 | * ---+---------+---+---+-----+----+--------------+ * C4'| |- |- |- |- |E9 | * ---+---------+---+---+-----+----+--------------+ * C5|E8 | | | | | | * ---+---------+---+---+-----+----+--------------+ * * Event definitions: * * -E1: Command was requested to be sent on wire * -E2: Command was submitted and now active on wire * -E3: Command was successfully completed * - SCSI command is move to completion queue * - ABORT/RESET/etc are completed. * -E4: Command has been requested to abort * - SCSI command in pending queue will be returned * to caller with aborted status. * - SCSI command state updated and iscsi_handle_abort() * will be called. * - SCSI command with ABORTING state has already * been requested to abort ignore request. * - ABORT/RESET commands will be destroyed and the * caller will be notify of the failure. * - All other commands will just be destroyed. * -E6: Command has timed out * - SCSI commands in pending queue will be returned up the * stack with TIMEOUT errors. * - SCSI commands in the active queue and timed out * will be moved to the aborting queue. * - SCSI commands in ABORTING state will be returned up * up the stack with TIMEOUT errors. * - ABORT/RESET commands will be destroyed and the caller * notified of the failure. * - All other commands will just be detroyed. * -E7: Connection has encountered a problem * -E8: Command has completed * - Only SCSI cmds should receive these events * and reach the command state. * -E9: Callback received for previous idm_task_abort request * -E10: The command this abort was associated with has terminated on its own */ void iscsi_cmd_state_machine(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg) { boolean_t release_lock = B_TRUE; ASSERT(icmdp != NULL); ASSERT(arg != NULL); DTRACE_PROBE3(event, iscsi_cmd_t *, icmdp, char *, iscsi_cmd_state_str(icmdp->cmd_state), char *, iscsi_cmd_event_str(event)); mutex_enter(&icmdp->cmd_mutex); /* Audit event */ idm_sm_audit_event(&icmdp->cmd_state_audit, SAS_ISCSI_CMD, icmdp->cmd_state, event, (uintptr_t)arg); icmdp->cmd_prev_state = icmdp->cmd_state; switch (icmdp->cmd_state) { case ISCSI_CMD_STATE_FREE: iscsi_cmd_state_free(icmdp, event, arg); break; case ISCSI_CMD_STATE_PENDING: iscsi_cmd_state_pending(icmdp, event, arg); break; case ISCSI_CMD_STATE_ACTIVE: iscsi_cmd_state_active(icmdp, event, arg); break; case ISCSI_CMD_STATE_ABORTING: iscsi_cmd_state_aborting(icmdp, event, arg); break; case ISCSI_CMD_STATE_IDM_ABORTING: iscsi_cmd_state_idm_aborting(icmdp, event, arg); break; case ISCSI_CMD_STATE_COMPLETED: iscsi_cmd_state_completed(icmdp, event, arg); /* * Once completed event is processed we DO NOT * want to touch it again because the caller * (sd, st, etc) may have freed the command. */ release_lock = B_FALSE; break; default: ASSERT(FALSE); } if (release_lock == B_TRUE) { /* Audit state if not completed */ idm_sm_audit_state_change(&icmdp->cmd_state_audit, SAS_ISCSI_CMD, icmdp->cmd_prev_state, icmdp->cmd_state); if (!(icmdp->cmd_misc_flags & ISCSI_CMD_MISCFLAG_FREE) || !(icmdp->cmd_misc_flags & ISCSI_CMD_MISCFLAG_INTERNAL)) { mutex_exit(&icmdp->cmd_mutex); return; } mutex_exit(&icmdp->cmd_mutex); iscsi_cmd_free(icmdp); } } /* * iscsi_cmd_alloc - * */ iscsi_cmd_t * iscsi_cmd_alloc(iscsi_conn_t *icp, int km_flags) { iscsi_cmd_t *icmdp; icmdp = kmem_zalloc(sizeof (iscsi_cmd_t), km_flags); if (icmdp) { icmdp->cmd_sig = ISCSI_SIG_CMD; icmdp->cmd_state = ISCSI_CMD_STATE_FREE; icmdp->cmd_conn = icp; icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_INTERNAL; idm_sm_audit_init(&icmdp->cmd_state_audit); mutex_init(&icmdp->cmd_mutex, NULL, MUTEX_DRIVER, NULL); cv_init(&icmdp->cmd_completion, NULL, CV_DRIVER, NULL); } return (icmdp); } /* * iscsi_cmd_free - * */ void iscsi_cmd_free(iscsi_cmd_t *icmdp) { ASSERT(icmdp != NULL); ASSERT(icmdp->cmd_sig == ISCSI_SIG_CMD); ASSERT(icmdp->cmd_state == ISCSI_CMD_STATE_FREE); ASSERT(icmdp->cmd_next == NULL); ASSERT(icmdp->cmd_prev == NULL); ASSERT(icmdp->cmd_misc_flags & ISCSI_CMD_MISCFLAG_INTERNAL); if (icmdp->cmd_type == ISCSI_CMD_TYPE_ABORT) ASSERT(icmdp->cmd_un.abort.icmdp == NULL); else if (icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) { ASSERT(icmdp->cmd_un.scsi.r2t_icmdp == NULL); ASSERT(icmdp->cmd_un.scsi.abort_icmdp == NULL); } mutex_destroy(&icmdp->cmd_mutex); cv_destroy(&icmdp->cmd_completion); kmem_free(icmdp, sizeof (iscsi_cmd_t)); } /* * +--------------------------------------------------------------------+ * | Internal Command Interfaces | * +--------------------------------------------------------------------+ */ /* * iscsi_cmd_state_free - * */ static void iscsi_cmd_state_free(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg) { iscsi_sess_t *isp = (iscsi_sess_t *)arg; ASSERT(icmdp != NULL); ASSERT(icmdp->cmd_state == ISCSI_CMD_STATE_FREE); ASSERT(isp != NULL); /* switch on event change */ switch (event) { /* -E1: Command was requested to be sent on wire */ case ISCSI_CMD_EVENT_E1: /* setup timestamps and timeouts for this command */ icmdp->cmd_lbolt_pending = ddi_get_lbolt(); if (icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) { /* * Establish absolute time when command should timeout. * For commands that depend on cmdsn window to go * active, the timeout will be ignored while on * the pending queue and a new timeout will be * established when the command goes active. */ if (icmdp->cmd_un.scsi.pkt && icmdp->cmd_un.scsi.pkt->pkt_time) icmdp->cmd_lbolt_timeout = icmdp->cmd_lbolt_pending + SEC_TO_TICK( icmdp->cmd_un.scsi.pkt->pkt_time * iscsi_cmd_timeout_factor); else icmdp->cmd_lbolt_timeout = 0; } else { icmdp->cmd_lbolt_timeout = icmdp->cmd_lbolt_pending + SEC_TO_TICK(ISCSI_INTERNAL_CMD_TIMEOUT * iscsi_cmd_timeout_factor); } /* place into pending queue */ iscsi_enqueue_pending_cmd(isp, icmdp); break; /* All other events are invalid for this state */ default: ASSERT(FALSE); } } /* * iscsi_cmd_state_pending - * */ static void iscsi_cmd_state_pending(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg) { iscsi_status_t status; iscsi_sess_t *isp = (iscsi_sess_t *)arg; boolean_t free_icmdp = B_FALSE; int rval; ASSERT(icmdp != NULL); ASSERT(icmdp->cmd_state == ISCSI_CMD_STATE_PENDING); ASSERT(isp != NULL); /* switch on event change */ switch (event) { /* -E2: Command was submitted and now active on wire */ case ISCSI_CMD_EVENT_E2: /* A connection should have already been assigned */ ASSERT(mutex_owned(&isp->sess_queue_pending.mutex)); ASSERT(icmdp->cmd_conn != NULL); /* * RESERVE RESOURSES */ switch (icmdp->cmd_type) { case ISCSI_CMD_TYPE_SCSI: /* check cmdsn window */ mutex_enter(&isp->sess_cmdsn_mutex); if (!iscsi_sna_lte(isp->sess_cmdsn, isp->sess_maxcmdsn)) { /* cmdsn window closed */ mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&isp->sess_queue_pending.mutex); isp->sess_window_open = B_FALSE; icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_STUCK; return; } /* assign itt */ status = iscsi_sess_reserve_scsi_itt(icmdp); if (!ISCSI_SUCCESS(status)) { /* no available itt slots */ mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&isp->sess_queue_pending.mutex); isp->sess_window_open = B_FALSE; icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_STUCK; return; } mutex_exit(&isp->sess_cmdsn_mutex); break; case ISCSI_CMD_TYPE_ABORT: /* * Verify ABORT's parent SCSI command is still * there. If parent SCSI command is completed * then there is no longer any reason to abort * the parent command. This could occur due * to a connection or target reset. */ ASSERT(icmdp->cmd_un.abort.icmdp != NULL); if (icmdp->cmd_un.abort.icmdp->cmd_state == ISCSI_CMD_STATE_COMPLETED) { iscsi_dequeue_pending_cmd(isp, icmdp); mutex_exit(&isp->sess_queue_pending.mutex); mutex_enter(&icmdp->cmd_un.abort.icmdp-> cmd_mutex); icmdp->cmd_un.abort.icmdp-> cmd_un.scsi.abort_icmdp = NULL; cv_broadcast(&icmdp->cmd_un.abort.icmdp-> cmd_completion); mutex_exit(&icmdp->cmd_un.abort.icmdp-> cmd_mutex); icmdp->cmd_un.abort.icmdp = NULL; icmdp->cmd_state = ISCSI_CMD_STATE_FREE; icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_FREE; return; } /* FALLTHRU */ case ISCSI_CMD_TYPE_RESET: /* FALLTHRU */ case ISCSI_CMD_TYPE_LOGOUT: mutex_enter(&isp->sess_cmdsn_mutex); /* assign itt */ status = iscsi_sess_reserve_itt(isp, icmdp); if (!ISCSI_SUCCESS(status)) { /* no available itt slots */ mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&isp->sess_queue_pending.mutex); isp->sess_window_open = B_FALSE; return; } mutex_exit(&isp->sess_cmdsn_mutex); break; case ISCSI_CMD_TYPE_NOP: /* assign itt, if needed */ if (icmdp->cmd_itt == ISCSI_RSVD_TASK_TAG) { /* not expecting a response */ free_icmdp = B_TRUE; } else { /* expecting response, assign an itt */ mutex_enter(&isp->sess_cmdsn_mutex); /* assign itt */ status = iscsi_sess_reserve_itt(isp, icmdp); if (!ISCSI_SUCCESS(status)) { /* no available itt slots */ mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&isp->sess_queue_pending. mutex); isp->sess_window_open = B_FALSE; return; } mutex_exit(&isp->sess_cmdsn_mutex); } break; case ISCSI_CMD_TYPE_TEXT: mutex_enter(&isp->sess_cmdsn_mutex); /* check cmdsn window */ if (!iscsi_sna_lte(isp->sess_cmdsn, isp->sess_maxcmdsn)) { /* cmdsn window closed */ isp->sess_window_open = B_FALSE; mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&isp->sess_queue_pending.mutex); icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_STUCK; return; } if (icmdp->cmd_un.text.stage == ISCSI_CMD_TEXT_INITIAL_REQ) { /* assign itt */ status = iscsi_sess_reserve_itt(isp, icmdp); if (!ISCSI_SUCCESS(status)) { /* no available itt slots */ mutex_exit(&isp->sess_cmdsn_mutex); mutex_exit(&isp->sess_queue_pending. mutex); isp->sess_window_open = B_FALSE; icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_STUCK; return; } } mutex_exit(&isp->sess_cmdsn_mutex); break; default: ASSERT(FALSE); } /* * RESOURCES RESERVED * * Now that we have the resources reserved, establish timeout * for cmd_type values that depend on having an open cmdsn * window (i.e. cmd_type that called iscsi_sna_lte() above). */ if (icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI) { if (icmdp->cmd_un.scsi.pkt && icmdp->cmd_un.scsi.pkt->pkt_time) icmdp->cmd_lbolt_timeout = ddi_get_lbolt() + SEC_TO_TICK( icmdp->cmd_un.scsi.pkt->pkt_time * iscsi_cmd_timeout_factor); else icmdp->cmd_lbolt_timeout = 0; } else if (icmdp->cmd_type == ISCSI_CMD_TYPE_TEXT) { icmdp->cmd_lbolt_timeout = ddi_get_lbolt() + SEC_TO_TICK(ISCSI_INTERNAL_CMD_TIMEOUT * iscsi_cmd_timeout_factor); } /* remove command from pending queue */ iscsi_dequeue_pending_cmd(isp, icmdp); /* check if expecting a response */ if (free_icmdp == B_FALSE) { /* response expected, move to active queue */ mutex_enter(&icmdp->cmd_conn->conn_queue_active.mutex); iscsi_enqueue_active_cmd(icmdp->cmd_conn, icmdp); mutex_exit(&icmdp->cmd_conn->conn_queue_active.mutex); } /* * TRANSFER COMMAND */ rval = iscsi_tx_cmd(isp, icmdp); ASSERT(!mutex_owned(&isp->sess_queue_pending.mutex)); /* * CHECK SUCCESS/FAILURE */ if (!ISCSI_SUCCESS(rval)) { /* * iscsi_tx_cmd failed. No cleanup is required * of commands that were put in the active queue. * If the tx failed then rx will also fail and cleanup * all items in the active/aborted queue in a common. */ /* EMPTY */ } /* free temporary commands */ if (free_icmdp == B_TRUE) { icmdp->cmd_state = ISCSI_CMD_STATE_FREE; icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_FREE; } break; /* -E10: Abort is no longer required for this command */ case ISCSI_CMD_EVENT_E10: /* * Acquiring the sess_queue_pending lock while the * conn_queue_active lock is held conflicts with the * locking order in iscsi_cmd_state_pending where * conn_queue_active is acquired while sess_queue_pending * is held. Normally this would be a dangerous lock * order conflict, except that we know that if we are * seeing ISCSI_CMD_EVENT_E10 then the command being * aborted is in "aborting" state and by extension * is not in "pending" state. Therefore the code * path with that alternate lock order will not execute. * That's good because we can't drop the lock here without * risking a deadlock. */ ASSERT(mutex_owned(&icmdp->cmd_conn->conn_queue_active.mutex)); mutex_enter(&isp->sess_queue_pending.mutex); icmdp->cmd_lbolt_aborting = ddi_get_lbolt(); iscsi_dequeue_pending_cmd(isp, icmdp); icmdp->cmd_un.abort.icmdp->cmd_un.scsi.abort_icmdp = NULL; icmdp->cmd_un.abort.icmdp = NULL; icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_FREE; icmdp->cmd_state = ISCSI_CMD_STATE_FREE; mutex_exit(&isp->sess_queue_pending.mutex); break; /* -E4: Command has been requested to abort */ case ISCSI_CMD_EVENT_E4: ASSERT(mutex_owned(&isp->sess_queue_pending.mutex)); icmdp->cmd_lbolt_aborting = ddi_get_lbolt(); ISCSI_CMD_SET_REASON_STAT(icmdp, CMD_ABORTED, STAT_ABORTED); iscsi_dequeue_pending_cmd(isp, icmdp); iscsi_enqueue_completed_cmd(isp, icmdp); icmdp->cmd_lbolt_aborting = ddi_get_lbolt(); break; /* -E7: Command has been reset */ case ISCSI_CMD_EVENT_E7: /* FALLTHRU */ /* -E6: Command has timed out */ case ISCSI_CMD_EVENT_E6: ASSERT(mutex_owned(&isp->sess_queue_pending.mutex)); iscsi_dequeue_pending_cmd(isp, icmdp); switch (icmdp->cmd_type) { case ISCSI_CMD_TYPE_SCSI: /* Complete to caller as TIMEOUT */ if (event == ISCSI_CMD_EVENT_E6) { ISCSI_CMD_SET_REASON_STAT(icmdp, CMD_TIMEOUT, STAT_TIMEOUT); } else { ISCSI_CMD_SET_REASON_STAT(icmdp, CMD_TRAN_ERR, 0); } iscsi_enqueue_completed_cmd(isp, icmdp); break; case ISCSI_CMD_TYPE_NOP: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; /* * Timeout occured. Just free NOP. Another * NOP request will be spawned to replace * this one. */ icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_FREE; break; case ISCSI_CMD_TYPE_ABORT: mutex_enter(&icmdp->cmd_un.abort.icmdp->cmd_mutex); icmdp->cmd_un.abort.icmdp-> cmd_un.scsi.abort_icmdp = NULL; cv_broadcast(&icmdp->cmd_un.abort.icmdp-> cmd_completion); mutex_exit(&icmdp->cmd_un.abort.icmdp->cmd_mutex); icmdp->cmd_un.abort.icmdp = NULL; icmdp->cmd_state = ISCSI_CMD_STATE_FREE; icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_FREE; break; case ISCSI_CMD_TYPE_RESET: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; /* * If we are failing a RESET we need * to notify the tran_reset caller. * with the cmd and notify caller. */ ISCSI_CMD_ISSUE_CALLBACK(icmdp, ISCSI_STATUS_CMD_FAILED); break; case ISCSI_CMD_TYPE_LOGOUT: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; /* notify requester of failure */ ISCSI_CMD_ISSUE_CALLBACK(icmdp, ISCSI_STATUS_CMD_FAILED); break; case ISCSI_CMD_TYPE_TEXT: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; icmdp->cmd_un.text.stage = ISCSI_CMD_TEXT_FINAL_RSP; /* * If a TEXT command fails, notify the owner. */ ISCSI_CMD_ISSUE_CALLBACK(icmdp, ISCSI_STATUS_CMD_FAILED); break; default: ASSERT(FALSE); break; } break; /* All other events are invalid for this state */ default: ASSERT(FALSE); } } /* * iscsi_cmd_state_active - * */ static void iscsi_cmd_state_active(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg) { iscsi_sess_t *isp = (iscsi_sess_t *)arg; iscsi_hba_t *ihp; iscsi_cmd_t *t_icmdp = NULL; iscsi_conn_t *icp = NULL; ASSERT(icmdp != NULL); ASSERT(icmdp->cmd_state == ISCSI_CMD_STATE_ACTIVE); ASSERT(isp != NULL); ihp = isp->sess_hba; ASSERT(ihp != NULL); icp = icmdp->cmd_conn; ASSERT(icp != NULL); ASSERT(mutex_owned(&icp->conn_queue_active.mutex)); /* switch on event change */ switch (event) { /* -E3: Command was successfully completed */ case ISCSI_CMD_EVENT_E3: /* * Remove command from the active list. We need to protect * someone from looking up this command ITT until it's * freed of the command is moved to a new queue location. */ mutex_enter(&isp->sess_cmdsn_mutex); iscsi_dequeue_active_cmd(icmdp->cmd_conn, icmdp); switch (icmdp->cmd_type) { case ISCSI_CMD_TYPE_SCSI: iscsi_sess_release_scsi_itt(icmdp); mutex_exit(&isp->sess_cmdsn_mutex); iscsi_enqueue_completed_cmd(isp, icmdp); break; case ISCSI_CMD_TYPE_NOP: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; iscsi_sess_release_itt(isp, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); /* free alloc */ icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_FREE; break; case ISCSI_CMD_TYPE_ABORT: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; iscsi_sess_release_itt(isp, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); /* * Abort was completed successfully. We should * complete the parent scsi command if it still * exists as timed out, and the state is not * COMPLETED */ t_icmdp = icmdp->cmd_un.abort.icmdp; ASSERT(t_icmdp != NULL); mutex_enter(&t_icmdp->cmd_mutex); t_icmdp->cmd_un.scsi.abort_icmdp = NULL; if (t_icmdp->cmd_state != ISCSI_CMD_STATE_COMPLETED) { iscsi_dequeue_active_cmd( t_icmdp->cmd_conn, t_icmdp); mutex_enter( &icp->conn_queue_idm_aborting.mutex); iscsi_enqueue_idm_aborting_cmd( t_icmdp->cmd_conn, t_icmdp); mutex_exit(&icp->conn_queue_idm_aborting.mutex); /* * Complete abort processing after IDM * calls us back. Set the status to use * when we complete the command. */ ISCSI_CMD_SET_REASON_STAT( t_icmdp, CMD_TIMEOUT, STAT_TIMEOUT); idm_task_abort(icp->conn_ic, t_icmdp->cmd_itp, AT_TASK_MGMT_ABORT); } else { cv_broadcast(&t_icmdp->cmd_completion); } mutex_exit(&t_icmdp->cmd_mutex); icmdp->cmd_un.abort.icmdp = NULL; icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_FREE; break; case ISCSI_CMD_TYPE_RESET: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; iscsi_sess_release_itt(isp, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); /* * Complete the abort/reset successfully. */ ISCSI_CMD_ISSUE_CALLBACK(icmdp, ISCSI_STATUS_SUCCESS); break; case ISCSI_CMD_TYPE_LOGOUT: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; iscsi_sess_release_itt(isp, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); /* * Complete the logout successfully. */ ISCSI_CMD_ISSUE_CALLBACK(icmdp, ISCSI_STATUS_SUCCESS); break; case ISCSI_CMD_TYPE_TEXT: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; if (icmdp->cmd_un.text.stage == ISCSI_CMD_TEXT_FINAL_RSP) { iscsi_sess_release_itt(isp, icmdp); } mutex_exit(&isp->sess_cmdsn_mutex); /* * Complete the text command successfully. */ ISCSI_CMD_ISSUE_CALLBACK(icmdp, icmdp->cmd_result); break; default: mutex_exit(&isp->sess_cmdsn_mutex); ASSERT(FALSE); } ASSERT(!mutex_owned(&isp->sess_cmdsn_mutex)); break; /* -E10,E4: Command has been requested to abort */ case ISCSI_CMD_EVENT_E10: /* FALLTHRU */ case ISCSI_CMD_EVENT_E4: /* E4 is only for resets and aborts */ ASSERT((icmdp->cmd_type == ISCSI_CMD_TYPE_ABORT) || (icmdp->cmd_type == ISCSI_CMD_TYPE_RESET)); /* FALLTHRU */ /* -E6: Command has timed out */ case ISCSI_CMD_EVENT_E6: switch (icmdp->cmd_type) { case ISCSI_CMD_TYPE_SCSI: icmdp->cmd_state = ISCSI_CMD_STATE_ABORTING; iscsi_handle_abort(icmdp); break; case ISCSI_CMD_TYPE_NOP: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; mutex_enter(&isp->sess_cmdsn_mutex); iscsi_sess_release_itt(isp, icmdp); iscsi_dequeue_active_cmd(icmdp->cmd_conn, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_FREE; break; case ISCSI_CMD_TYPE_ABORT: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; mutex_enter(&isp->sess_cmdsn_mutex); iscsi_dequeue_active_cmd(icmdp->cmd_conn, icmdp); iscsi_sess_release_itt(isp, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); /* * If this is an E4 then we may need to deal with * the abort's associated SCSI command. If this * is an E10 then IDM is already cleaning up the * SCSI command and all we need to do is break the * linkage between them and free the abort command. */ t_icmdp = icmdp->cmd_un.abort.icmdp; ASSERT(t_icmdp != NULL); if (event != ISCSI_CMD_EVENT_E10) { mutex_enter(&t_icmdp->cmd_mutex); t_icmdp->cmd_un.scsi.abort_icmdp = NULL; /* * If abort command is aborted then we should * not act on the parent scsi command. If the * abort command timed out then we need to * complete the parent command if it still * exists with a timeout failure. */ if ((event == ISCSI_CMD_EVENT_E6) && (t_icmdp->cmd_state != ISCSI_CMD_STATE_IDM_ABORTING) && (t_icmdp->cmd_state != ISCSI_CMD_STATE_COMPLETED)) { iscsi_dequeue_active_cmd( t_icmdp->cmd_conn, t_icmdp); mutex_enter(&icp-> conn_queue_idm_aborting.mutex); iscsi_enqueue_idm_aborting_cmd( t_icmdp->cmd_conn, t_icmdp); mutex_exit(&icp-> conn_queue_idm_aborting.mutex); /* * Complete abort processing after IDM * calls us back. Set the status to use * when we complete the command. */ ISCSI_CMD_SET_REASON_STAT(t_icmdp, CMD_TIMEOUT, STAT_TIMEOUT); idm_task_abort(icp->conn_ic, t_icmdp->cmd_itp, AT_TASK_MGMT_ABORT); } else { cv_broadcast(&t_icmdp->cmd_completion); } mutex_exit(&t_icmdp->cmd_mutex); } else { t_icmdp->cmd_un.scsi.abort_icmdp = NULL; } icmdp->cmd_un.abort.icmdp = NULL; icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_FREE; break; case ISCSI_CMD_TYPE_RESET: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; mutex_enter(&isp->sess_cmdsn_mutex); iscsi_sess_release_itt(isp, icmdp); iscsi_dequeue_active_cmd(icmdp->cmd_conn, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); /* * If we are failing a RESET we need * to notify the tran_reset caller. * It will free the memory associated * with the cmd and notify caller. */ ISCSI_CMD_ISSUE_CALLBACK(icmdp, ISCSI_STATUS_CMD_FAILED); break; case ISCSI_CMD_TYPE_LOGOUT: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; mutex_enter(&isp->sess_cmdsn_mutex); iscsi_sess_release_itt(isp, icmdp); iscsi_dequeue_active_cmd(icmdp->cmd_conn, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); /* * Notify caller of failure. */ ISCSI_CMD_ISSUE_CALLBACK(icmdp, ISCSI_STATUS_CMD_FAILED); break; case ISCSI_CMD_TYPE_TEXT: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; icmdp->cmd_un.text.stage = ISCSI_CMD_TEXT_FINAL_RSP; mutex_enter(&isp->sess_cmdsn_mutex); iscsi_sess_release_itt(isp, icmdp); iscsi_dequeue_active_cmd(icmdp->cmd_conn, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); /* * If a TEXT command fails, notify caller so * it can free assocated command */ ISCSI_CMD_ISSUE_CALLBACK(icmdp, ISCSI_STATUS_CMD_FAILED); break; default: ASSERT(FALSE); } ASSERT(!mutex_owned(&isp->sess_cmdsn_mutex)); break; /* -E7: Connection has encountered a problem */ case ISCSI_CMD_EVENT_E7: mutex_enter(&isp->sess_cmdsn_mutex); iscsi_dequeue_active_cmd(icmdp->cmd_conn, icmdp); switch (icmdp->cmd_type) { case ISCSI_CMD_TYPE_SCSI: mutex_exit(&isp->sess_cmdsn_mutex); mutex_enter(&icp->conn_queue_idm_aborting.mutex); iscsi_enqueue_idm_aborting_cmd(icmdp->cmd_conn, icmdp); mutex_exit(&icp->conn_queue_idm_aborting.mutex); ISCSI_CMD_SET_REASON_STAT(icmdp, CMD_TRAN_ERR, 0); idm_task_abort(icp->conn_ic, icmdp->cmd_itp, AT_TASK_MGMT_ABORT); break; case ISCSI_CMD_TYPE_NOP: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; iscsi_sess_release_itt(isp, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_FREE; break; case ISCSI_CMD_TYPE_ABORT: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; iscsi_sess_release_itt(isp, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); mutex_enter(&icmdp->cmd_un.abort.icmdp->cmd_mutex); icmdp->cmd_un.abort.icmdp-> cmd_un.scsi.abort_icmdp = NULL; cv_broadcast(&icmdp->cmd_un.abort.icmdp-> cmd_completion); mutex_exit(&icmdp->cmd_un.abort.icmdp->cmd_mutex); /* * Nullify the abort command's pointer to its * parent command. It does not have to complete its * parent command because the parent command will * also get an E7. */ icmdp->cmd_un.abort.icmdp = NULL; icmdp->cmd_misc_flags |= ISCSI_CMD_MISCFLAG_FREE; break; case ISCSI_CMD_TYPE_RESET: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; iscsi_sess_release_itt(isp, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); /* * If we are failing a ABORT we need * to notify the tran_abort caller. * It will free the memory associated * with the cmd and notify caller. */ ISCSI_CMD_ISSUE_CALLBACK(icmdp, ISCSI_STATUS_CMD_FAILED); break; case ISCSI_CMD_TYPE_LOGOUT: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; /* * A connection problem and we attempted to * logout? I guess we can just free the * request. Someone has already pushed the * connection state. */ iscsi_sess_release_itt(isp, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); ISCSI_CMD_ISSUE_CALLBACK(icmdp, ISCSI_STATUS_SUCCESS); break; case ISCSI_CMD_TYPE_TEXT: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; icmdp->cmd_un.text.stage = ISCSI_CMD_TEXT_FINAL_RSP; iscsi_sess_release_itt(isp, icmdp); mutex_exit(&isp->sess_cmdsn_mutex); /* * If a TEXT command fails, notify caller so * it can free assocated command */ ISCSI_CMD_ISSUE_CALLBACK(icmdp, ISCSI_STATUS_CMD_FAILED); break; default: mutex_exit(&isp->sess_cmdsn_mutex); ASSERT(FALSE); break; } ASSERT(!mutex_owned(&isp->sess_cmdsn_mutex)); break; /* -E9: IDM is no longer processing this command */ case ISCSI_CMD_EVENT_E9: iscsi_dequeue_active_cmd(icmdp->cmd_conn, icmdp); iscsi_task_cleanup(ISCSI_OP_SCSI_RSP, icmdp); iscsi_sess_release_scsi_itt(icmdp); ISCSI_CMD_SET_REASON_STAT(icmdp, CMD_TRAN_ERR, 0); iscsi_enqueue_completed_cmd(isp, icmdp); break; /* All other events are invalid for this state */ default: ASSERT(FALSE); } } /* * iscsi_cmd_state_aborting - * */ static void iscsi_cmd_state_aborting(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg) { iscsi_sess_t *isp = (iscsi_sess_t *)arg; iscsi_cmd_t *a_icmdp; ASSERT(icmdp != NULL); ASSERT(icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI); ASSERT(icmdp->cmd_state == ISCSI_CMD_STATE_ABORTING); ASSERT(isp != NULL); ASSERT(mutex_owned(&icmdp->cmd_conn->conn_queue_active.mutex)); /* switch on event change */ switch (event) { /* -E3: Command was successfully completed */ case ISCSI_CMD_EVENT_E3: /* * Remove command from the aborting list */ mutex_enter(&isp->sess_cmdsn_mutex); iscsi_dequeue_active_cmd(icmdp->cmd_conn, icmdp); iscsi_sess_release_scsi_itt(icmdp); mutex_exit(&isp->sess_cmdsn_mutex); iscsi_enqueue_completed_cmd(isp, icmdp); break; /* -E4: Command has been requested to abort */ case ISCSI_CMD_EVENT_E4: /* * An upper level driver might attempt to * abort a command that we are already * aborting due to a nop. Since we are * already in the process of aborting * ignore the request. */ break; /* -E6: Command has timed out */ case ISCSI_CMD_EVENT_E6: ASSERT(FALSE); /* * Timeouts should not occur on command in abort queue * they are already be processed due to a timeout. */ break; /* -E7: Connection has encountered a problem */ case ISCSI_CMD_EVENT_E7: iscsi_dequeue_active_cmd(icmdp->cmd_conn, icmdp); mutex_enter(&icmdp->cmd_conn->conn_queue_idm_aborting.mutex); iscsi_enqueue_idm_aborting_cmd(icmdp->cmd_conn, icmdp); mutex_exit(&icmdp->cmd_conn->conn_queue_idm_aborting.mutex); /* * Since we are in "aborting" state there is another command * representing the abort of this command. This command * will cleanup at some indeterminate time after the call * to idm_task_abort so we can't leave the abort request * active. An E10 event to the abort command will cause * it to complete immediately. */ if ((a_icmdp = icmdp->cmd_un.scsi.abort_icmdp) != NULL) { iscsi_cmd_state_machine(a_icmdp, ISCSI_CMD_EVENT_E10, arg); } ISCSI_CMD_SET_REASON_STAT(icmdp, CMD_TRAN_ERR, 0); idm_task_abort(icmdp->cmd_conn->conn_ic, icmdp->cmd_itp, AT_TASK_MGMT_ABORT); break; /* -E9: IDM is no longer processing this command */ case ISCSI_CMD_EVENT_E9: iscsi_dequeue_active_cmd(icmdp->cmd_conn, icmdp); iscsi_task_cleanup(ISCSI_OP_SCSI_RSP, icmdp); iscsi_sess_release_scsi_itt(icmdp); ISCSI_CMD_SET_REASON_STAT(icmdp, CMD_TRAN_ERR, 0); iscsi_enqueue_completed_cmd(isp, icmdp); break; /* All other events are invalid for this state */ default: ASSERT(FALSE); } } static void iscsi_cmd_state_idm_aborting(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg) { iscsi_sess_t *isp = (iscsi_sess_t *)arg; ASSERT(icmdp != NULL); ASSERT(icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI); ASSERT(icmdp->cmd_state == ISCSI_CMD_STATE_IDM_ABORTING); ASSERT(isp != NULL); /* switch on event change */ switch (event) { /* -E3: Command was successfully completed */ case ISCSI_CMD_EVENT_E3: /* * iscsi_rx_process_cmd_rsp() and iscsi_rx_process_data_rsp() * are supposed to confirm the cmd state is appropriate before * generating an E3 event. E3 is not allowed in this state. */ ASSERT(0); break; /* -E4: Command has been requested to abort */ case ISCSI_CMD_EVENT_E4: /* * An upper level driver might attempt to * abort a command that we are already * aborting due to a nop. Since we are * already in the process of aborting * ignore the request. */ break; /* -E6: Command has timed out */ case ISCSI_CMD_EVENT_E6: ASSERT(FALSE); /* * Timeouts should not occur on aborting commands */ break; /* -E7: Connection has encountered a problem */ case ISCSI_CMD_EVENT_E7: /* * We have already requested IDM to stop processing this * command so ignore this request. */ break; /* -E9: IDM is no longer processing this command */ case ISCSI_CMD_EVENT_E9: mutex_enter(&icmdp->cmd_conn->conn_queue_idm_aborting.mutex); iscsi_dequeue_idm_aborting_cmd(icmdp->cmd_conn, icmdp); mutex_exit(&icmdp->cmd_conn->conn_queue_idm_aborting.mutex); /* This is always an error so make sure an error has been set */ ASSERT(icmdp->cmd_un.scsi.pkt->pkt_reason != CMD_CMPLT); iscsi_task_cleanup(ISCSI_OP_SCSI_RSP, icmdp); iscsi_sess_release_scsi_itt(icmdp); /* * Whoever called idm_task_abort should have set the completion * status beforehand. */ iscsi_enqueue_completed_cmd(isp, icmdp); cv_broadcast(&icmdp->cmd_completion); break; /* All other events are invalid for this state */ default: ASSERT(FALSE); } } /* * iscsi_cmd_state_completed - * */ static void iscsi_cmd_state_completed(iscsi_cmd_t *icmdp, iscsi_cmd_event_t event, void *arg) { iscsi_sess_t *isp = (iscsi_sess_t *)arg; ASSERT(icmdp != NULL); ASSERT(icmdp->cmd_type == ISCSI_CMD_TYPE_SCSI); ASSERT(icmdp->cmd_state == ISCSI_CMD_STATE_COMPLETED); ASSERT(isp != NULL); /* switch on event change */ switch (event) { /* -E8: */ case ISCSI_CMD_EVENT_E8: icmdp->cmd_state = ISCSI_CMD_STATE_FREE; /* the caller has already remove cmd from queue */ icmdp->cmd_next = NULL; icmdp->cmd_prev = NULL; iscsi_iodone(isp, icmdp); break; /* All other events are invalid for this state */ default: ASSERT(FALSE); } } /* * iscsi_cmd_state_str - * */ static char * iscsi_cmd_state_str(iscsi_cmd_state_t state) { switch (state) { case ISCSI_CMD_STATE_FREE: return ("free"); case ISCSI_CMD_STATE_PENDING: return ("pending"); case ISCSI_CMD_STATE_ACTIVE: return ("active"); case ISCSI_CMD_STATE_ABORTING: return ("aborting"); case ISCSI_CMD_STATE_IDM_ABORTING: return ("idm-aborting"); case ISCSI_CMD_STATE_COMPLETED: return ("completed"); default: return ("unknown"); } } /* * iscsi_cmd_event_str - * */ static char * iscsi_cmd_event_str(iscsi_cmd_event_t event) { switch (event) { case ISCSI_CMD_EVENT_E1: return ("E1"); case ISCSI_CMD_EVENT_E2: return ("E2"); case ISCSI_CMD_EVENT_E3: return ("E3"); case ISCSI_CMD_EVENT_E4: return ("E4"); case ISCSI_CMD_EVENT_E6: return ("E6"); case ISCSI_CMD_EVENT_E7: return ("E7"); case ISCSI_CMD_EVENT_E8: return ("E8"); case ISCSI_CMD_EVENT_E9: return ("E9"); case ISCSI_CMD_EVENT_E10: return ("E10"); default: return ("unknown"); } } /* * iscsi_cmd_event_str - * */ static char * iscsi_cmd_type_str(iscsi_cmd_type_t type) { switch (type) { case ISCSI_CMD_TYPE_SCSI: return ("scsi"); case ISCSI_CMD_TYPE_NOP: return ("nop"); case ISCSI_CMD_TYPE_ABORT: return ("abort"); case ISCSI_CMD_TYPE_RESET: return ("reset"); case ISCSI_CMD_TYPE_LOGOUT: return ("logout"); default: return ("unknown"); } }