/*
* 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
* 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 */
/* LINTED E_STATIC_UNUSED */
/*
* The following private tunable, settable via
* set iscsi:iscsi_cmd_timeout_factor = 2
* 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.
*/
/*
* +--------------------------------------------------------------------+
* | 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
* -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.
* 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.
* 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
{
char *, iscsi_cmd_event_str(event));
/* Audit event */
case ISCSI_CMD_STATE_FREE:
break;
case ISCSI_CMD_STATE_PENDING:
break;
case ISCSI_CMD_STATE_ACTIVE:
break;
case ISCSI_CMD_STATE_ABORTING:
break;
break;
/*
* Once completed event is processed we DO NOT
* want to touch it again because the caller
* (sd, st, etc) may have freed the command.
*/
break;
default:
}
if (release_lock == B_TRUE) {
/* Audit state if not completed */
!(icmdp->cmd_misc_flags &
return;
}
}
}
/*
* iscsi_cmd_alloc -
*
*/
{
if (icmdp) {
}
return (icmdp);
}
/*
* iscsi_cmd_free -
*
*/
void
{
}
}
/*
* +--------------------------------------------------------------------+
* | Internal Command Interfaces |
* +--------------------------------------------------------------------+
*/
/*
* iscsi_cmd_state_free -
*
*/
static void
{
/* 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 */
/*
* 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.
*/
else
icmdp->cmd_lbolt_timeout = 0;
} else {
}
/* place into pending queue */
break;
/* All other events are invalid for this state */
default:
}
}
/*
* iscsi_cmd_state_pending -
*
*/
static void
{
int rval;
/* 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 */
/*
* RESERVE RESOURSES
*/
case ISCSI_CMD_TYPE_SCSI:
/* check cmdsn window */
isp->sess_maxcmdsn)) {
/* cmdsn window closed */
icmdp->cmd_misc_flags |=
return;
}
/* assign itt */
if (!ISCSI_SUCCESS(status)) {
/* no available itt slots */
icmdp->cmd_misc_flags |=
return;
}
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.
*/
icmdp->cmd_misc_flags |=
return;
}
/* FALLTHRU */
case ISCSI_CMD_TYPE_RESET:
/* FALLTHRU */
case ISCSI_CMD_TYPE_LOGOUT:
/* assign itt */
if (!ISCSI_SUCCESS(status)) {
/* no available itt slots */
return;
}
break;
case ISCSI_CMD_TYPE_NOP:
/* assign itt, if needed */
/* not expecting a response */
free_icmdp = B_TRUE;
} else {
/* expecting response, assign an itt */
/* assign itt */
if (!ISCSI_SUCCESS(status)) {
/* no available itt slots */
mutex);
return;
}
}
break;
case ISCSI_CMD_TYPE_TEXT:
/* check cmdsn window */
isp->sess_maxcmdsn)) {
/* cmdsn window closed */
icmdp->cmd_misc_flags |=
return;
}
/* assign itt */
if (!ISCSI_SUCCESS(status)) {
/* no available itt slots */
mutex);
icmdp->cmd_misc_flags |=
return;
}
}
break;
default:
}
/*
* 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).
*/
ddi_get_lbolt() + SEC_TO_TICK(
else
icmdp->cmd_lbolt_timeout = 0;
}
/* remove command from pending queue */
/* check if expecting a response */
if (free_icmdp == B_FALSE) {
/* response expected, move to active queue */
}
/*
* TRANSFER COMMAND
*/
/*
*/
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
*/
/* EMPTY */
}
/* free temporary commands */
if (free_icmdp == B_TRUE) {
}
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.
*/
break;
/* -E4: Command has been requested to abort */
case ISCSI_CMD_EVENT_E4:
break;
/* -E7: Command has been reset */
case ISCSI_CMD_EVENT_E7:
/* FALLTHRU */
/* -E6: Command has timed out */
case ISCSI_CMD_EVENT_E6:
case ISCSI_CMD_TYPE_SCSI:
/* Complete to caller as TIMEOUT */
if (event == ISCSI_CMD_EVENT_E6) {
} else {
}
break;
case ISCSI_CMD_TYPE_NOP:
/*
* Timeout occured. Just free NOP. Another
* NOP request will be spawned to replace
* this one.
*/
icmdp->cmd_misc_flags |=
break;
case ISCSI_CMD_TYPE_ABORT:
icmdp->cmd_misc_flags |=
break;
case ISCSI_CMD_TYPE_RESET:
/*
* If we are failing a RESET we need
* to notify the tran_reset caller.
* with the cmd and notify caller.
*/
break;
case ISCSI_CMD_TYPE_LOGOUT:
/* notify requester of failure */
break;
case ISCSI_CMD_TYPE_TEXT:
/*
* If a TEXT command fails, notify the owner.
*/
break;
default:
break;
}
break;
/* All other events are invalid for this state */
default:
}
}
/*
* iscsi_cmd_state_active -
*
*/
static void
{
/* 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.
*/
case ISCSI_CMD_TYPE_SCSI:
break;
case ISCSI_CMD_TYPE_NOP:
/* free alloc */
icmdp->cmd_misc_flags |=
break;
case ISCSI_CMD_TYPE_ABORT:
/*
* 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);
/*
* Complete abort processing after IDM
* calls us back. Set the status to use
* when we complete the command.
*/
} else {
}
icmdp->cmd_misc_flags |=
break;
case ISCSI_CMD_TYPE_RESET:
/*
*/
} else {
}
break;
case ISCSI_CMD_TYPE_LOGOUT:
/*
* Complete the logout successfully.
*/
break;
case ISCSI_CMD_TYPE_TEXT:
}
/*
* Complete the text command successfully.
*/
break;
default:
}
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 */
/* FALLTHRU */
/* -E6: Command has timed out */
case ISCSI_CMD_EVENT_E6:
case ISCSI_CMD_TYPE_SCSI:
break;
case ISCSI_CMD_TYPE_NOP:
icmdp->cmd_misc_flags |=
break;
case ISCSI_CMD_TYPE_ABORT:
/*
* 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.
*/
if (event != ISCSI_CMD_EVENT_E10) {
/*
* 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) &&
mutex_enter(&icp->
mutex_exit(&icp->
/*
* Complete abort processing after IDM
* calls us back. Set the status to use
* when we complete the command.
*/
} else {
}
} else {
}
icmdp->cmd_misc_flags |=
break;
case ISCSI_CMD_TYPE_RESET:
/*
* 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.
*/
break;
case ISCSI_CMD_TYPE_LOGOUT:
/*
* Notify caller of failure.
*/
break;
case ISCSI_CMD_TYPE_TEXT:
/*
* If a TEXT command fails, notify caller so
* it can free assocated command
*/
break;
default:
}
break;
/* -E7: Connection has encountered a problem */
case ISCSI_CMD_EVENT_E7:
case ISCSI_CMD_TYPE_SCSI:
break;
case ISCSI_CMD_TYPE_NOP:
icmdp->cmd_misc_flags |=
break;
case ISCSI_CMD_TYPE_ABORT:
/*
* 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_misc_flags |=
break;
case ISCSI_CMD_TYPE_RESET:
/*
* 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.
*/
break;
case ISCSI_CMD_TYPE_LOGOUT:
/*
* A connection problem and we attempted to
* logout? I guess we can just free the
* request. Someone has already pushed the
* connection state.
*/
break;
case ISCSI_CMD_TYPE_TEXT:
/*
* If a TEXT command fails, notify caller so
* it can free assocated command
*/
break;
default:
break;
}
break;
/* -E9: IDM is no longer processing this command */
case ISCSI_CMD_EVENT_E9:
break;
/* All other events are invalid for this state */
default:
}
}
/*
* iscsi_cmd_state_aborting -
*
*/
static void
{
/* switch on event change */
switch (event) {
/* -E3: Command was successfully completed */
case ISCSI_CMD_EVENT_E3:
/*
* Remove command from the aborting list
*/
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:
/*
* 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:
/*
* 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.
*/
}
break;
/* -E9: IDM is no longer processing this command */
case ISCSI_CMD_EVENT_E9:
break;
/* All other events are invalid for this state */
default:
}
}
static void
void *arg)
{
/* 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:
/*
* 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 just update the pkt_statistics.
*/
break;
/* -E9: IDM is no longer processing this command */
case ISCSI_CMD_EVENT_E9:
/* This is always an error so make sure an error has been set */
/*
* Whoever called idm_task_abort should have set the completion
* status beforehand.
*/
break;
/* All other events are invalid for this state */
default:
}
}
/*
* iscsi_cmd_state_completed -
*
*/
static void
{
/* switch on event change */
switch (event) {
/* -E8: */
case ISCSI_CMD_EVENT_E8:
/* the caller has already remove cmd from queue */
break;
/* All other events are invalid for this state */
default:
}
}
/*
* iscsi_cmd_state_str -
*
*/
static char *
{
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");
return ("idm-aborting");
return ("completed");
default:
return ("unknown");
}
}
/*
* iscsi_cmd_event_str -
*
*/
static char *
{
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 *
{
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");
}
}