iscsi_cmd.c revision fcf3ce441efd61da9bb2884968af01cb7c1452cc
/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* iSCSI command interfaces
*/
#include "iscsi.h"
/* internal interfaces */
/* LINTED E_STATIC_UNUSED */
#define ISCSI_INTERNAL_CMD_TIMEOUT 60
/*
* 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.
*/
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.
* 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 | |
* C| V | S |
* M| _______ | C |
* D+--------/ C3 \ | S |
* S E3/4/6/7\ /-------+ I |
* ---+---E3/4/6/7| |
* E4/6| | C |
* V | M |
* ------- | D |
* - >/ C4 \ | S |
* / \ /-------+ |
* | ---+---E3/6/7 | |
* | E4| V /E8
* ------ -------
* / C5 \
* \ /
* ---+---
*
* The state transition table is as follows:
*
* +---------+---+---+-----+---------+
* |C1 |C2 |C3 |C4 |C5 |
* ---+---------+---+---+-----+---------+
* C1| - |E1 | - | - | |
* ---+---------+---+---+-----+---------+
* C2|E4/6/7 |- |E2 | - |E4/6/7 |
* ---+---------+---+---+-----+---------+
* C3|E3/4/6/7 |- |- |E4/6 |E3/4/6/7 |
* ---+---------+---+---+-----+---------+
* C4| |- |- |E4 |E3/6/7 |
* ---+---------+---+---+-----+---------+
* 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.
*/
void
{
char *, iscsi_cmd_event_str(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;
/*
* 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) {
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 the 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 */
return;
}
/* assign itt */
if (!ISCSI_SUCCESS(status)) {
/* no available itt slots */
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.
*/
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_R2T:
/* no additional resources required */
free_icmdp = B_TRUE;
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 */
return;
}
/* assign itt */
if (!ISCSI_SUCCESS(status)) {
/* no available itt slots */
mutex);
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;
/* -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 {
CMD_TRAN_ERR, 0);
}
break;
case ISCSI_CMD_TYPE_R2T:
/*
* If this command is timing out then
* the SCSI command will be timing out
* also. Just free the memory.
*/
break;
case ISCSI_CMD_TYPE_NOP:
/*
* Timeout occured. Just free NOP. Another
* NOP request will be spawned to replace
* this one.
*/
break;
case ISCSI_CMD_TYPE_ABORT:
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_R2T:
/*
* R2T commands do not have responses
* so these command should never be
* placed in the active queue.
*/
break;
case ISCSI_CMD_TYPE_NOP:
/* free alloc */
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
*/
}
break;
case ISCSI_CMD_TYPE_RESET:
/*
*/
break;
case ISCSI_CMD_TYPE_LOGOUT:
/*
* Complete the logout successfully.
*/
break;
case ISCSI_CMD_TYPE_TEXT:
}
/*
* Complete the text command successfully.
*/
break;
default:
}
break;
/* -E4: Command has been requested to abort */
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_R2T:
/* should never get in active queue */
break;
case ISCSI_CMD_TYPE_NOP:
break;
case ISCSI_CMD_TYPE_ABORT:
/*
* 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);
t_icmdp);
t_icmdp);
}
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:
/* notify caller of error */
CMD_TRAN_ERR, 0);
break;
case ISCSI_CMD_TYPE_R2T:
/* should never get in active queue */
break;
case ISCSI_CMD_TYPE_NOP:
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.
*/
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;
/* 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:
/* complete io with error */
CMD_TRAN_ERR, 0);
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 ("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");
default:
return ("unknown");
}
}
/*
* iscsi_cmd_event_str -
*
*/
static char *
{
switch (type) {
case ISCSI_CMD_TYPE_SCSI:
return ("scsi");
case ISCSI_CMD_TYPE_R2T:
return ("r2t");
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");
}
}