2011-07-23 10:43:04 +04:00
/*******************************************************************************
* This file contains error recovery level two functions used by
* the iSCSI Target driver .
*
* \ u00a9 Copyright 2007 - 2011 RisingTide Systems LLC .
*
* Licensed to the Linux Foundation under the General Public License ( GPL ) version 2.
*
* Author : Nicholas A . Bellinger < nab @ linux - iscsi . org >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include <scsi/iscsi_proto.h>
# include <target/target_core_base.h>
2011-11-16 18:46:48 +04:00
# include <target/target_core_fabric.h>
2011-07-23 10:43:04 +04:00
# include "iscsi_target_core.h"
# include "iscsi_target_datain_values.h"
# include "iscsi_target_util.h"
# include "iscsi_target_erl0.h"
# include "iscsi_target_erl1.h"
# include "iscsi_target_erl2.h"
# include "iscsi_target.h"
/*
* FIXME : Does RData SNACK apply here as well ?
*/
void iscsit_create_conn_recovery_datain_values (
struct iscsi_cmd * cmd ,
2012-09-26 16:00:40 +04:00
__be32 exp_data_sn )
2011-07-23 10:43:04 +04:00
{
u32 data_sn = 0 ;
struct iscsi_conn * conn = cmd - > conn ;
cmd - > next_burst_len = 0 ;
cmd - > read_data_done = 0 ;
2012-09-26 16:00:40 +04:00
while ( be32_to_cpu ( exp_data_sn ) > data_sn ) {
2011-07-23 10:43:04 +04:00
if ( ( cmd - > next_burst_len +
conn - > conn_ops - > MaxRecvDataSegmentLength ) <
conn - > sess - > sess_ops - > MaxBurstLength ) {
cmd - > read_data_done + =
conn - > conn_ops - > MaxRecvDataSegmentLength ;
cmd - > next_burst_len + =
conn - > conn_ops - > MaxRecvDataSegmentLength ;
} else {
cmd - > read_data_done + =
( conn - > sess - > sess_ops - > MaxBurstLength -
cmd - > next_burst_len ) ;
cmd - > next_burst_len = 0 ;
}
data_sn + + ;
}
}
void iscsit_create_conn_recovery_dataout_values (
struct iscsi_cmd * cmd )
{
u32 write_data_done = 0 ;
struct iscsi_conn * conn = cmd - > conn ;
cmd - > data_sn = 0 ;
cmd - > next_burst_len = 0 ;
while ( cmd - > write_data_done > write_data_done ) {
if ( ( write_data_done + conn - > sess - > sess_ops - > MaxBurstLength ) < =
cmd - > write_data_done )
write_data_done + = conn - > sess - > sess_ops - > MaxBurstLength ;
else
break ;
}
cmd - > write_data_done = write_data_done ;
}
static int iscsit_attach_active_connection_recovery_entry (
struct iscsi_session * sess ,
struct iscsi_conn_recovery * cr )
{
spin_lock ( & sess - > cr_a_lock ) ;
list_add_tail ( & cr - > cr_list , & sess - > cr_active_list ) ;
spin_unlock ( & sess - > cr_a_lock ) ;
return 0 ;
}
static int iscsit_attach_inactive_connection_recovery_entry (
struct iscsi_session * sess ,
struct iscsi_conn_recovery * cr )
{
spin_lock ( & sess - > cr_i_lock ) ;
list_add_tail ( & cr - > cr_list , & sess - > cr_inactive_list ) ;
sess - > conn_recovery_count + + ;
pr_debug ( " Incremented connection recovery count to %u for "
" SID: %u \n " , sess - > conn_recovery_count , sess - > sid ) ;
spin_unlock ( & sess - > cr_i_lock ) ;
return 0 ;
}
struct iscsi_conn_recovery * iscsit_get_inactive_connection_recovery_entry (
struct iscsi_session * sess ,
u16 cid )
{
struct iscsi_conn_recovery * cr ;
spin_lock ( & sess - > cr_i_lock ) ;
list_for_each_entry ( cr , & sess - > cr_inactive_list , cr_list ) {
if ( cr - > cid = = cid ) {
spin_unlock ( & sess - > cr_i_lock ) ;
return cr ;
}
}
spin_unlock ( & sess - > cr_i_lock ) ;
return NULL ;
}
void iscsit_free_connection_recovery_entires ( struct iscsi_session * sess )
{
struct iscsi_cmd * cmd , * cmd_tmp ;
struct iscsi_conn_recovery * cr , * cr_tmp ;
spin_lock ( & sess - > cr_a_lock ) ;
list_for_each_entry_safe ( cr , cr_tmp , & sess - > cr_active_list , cr_list ) {
list_del ( & cr - > cr_list ) ;
spin_unlock ( & sess - > cr_a_lock ) ;
spin_lock ( & cr - > conn_recovery_cmd_lock ) ;
list_for_each_entry_safe ( cmd , cmd_tmp ,
2012-04-04 02:51:01 +04:00
& cr - > conn_recovery_cmd_list , i_conn_node ) {
2011-07-23 10:43:04 +04:00
2012-04-04 02:51:01 +04:00
list_del ( & cmd - > i_conn_node ) ;
2011-07-23 10:43:04 +04:00
cmd - > conn = NULL ;
spin_unlock ( & cr - > conn_recovery_cmd_lock ) ;
2011-10-09 12:48:14 +04:00
iscsit_free_cmd ( cmd ) ;
2011-07-23 10:43:04 +04:00
spin_lock ( & cr - > conn_recovery_cmd_lock ) ;
}
spin_unlock ( & cr - > conn_recovery_cmd_lock ) ;
spin_lock ( & sess - > cr_a_lock ) ;
kfree ( cr ) ;
}
spin_unlock ( & sess - > cr_a_lock ) ;
spin_lock ( & sess - > cr_i_lock ) ;
list_for_each_entry_safe ( cr , cr_tmp , & sess - > cr_inactive_list , cr_list ) {
list_del ( & cr - > cr_list ) ;
spin_unlock ( & sess - > cr_i_lock ) ;
spin_lock ( & cr - > conn_recovery_cmd_lock ) ;
list_for_each_entry_safe ( cmd , cmd_tmp ,
2012-04-04 02:51:01 +04:00
& cr - > conn_recovery_cmd_list , i_conn_node ) {
2011-07-23 10:43:04 +04:00
2012-04-04 02:51:01 +04:00
list_del ( & cmd - > i_conn_node ) ;
2011-07-23 10:43:04 +04:00
cmd - > conn = NULL ;
spin_unlock ( & cr - > conn_recovery_cmd_lock ) ;
2011-10-09 12:48:14 +04:00
iscsit_free_cmd ( cmd ) ;
2011-07-23 10:43:04 +04:00
spin_lock ( & cr - > conn_recovery_cmd_lock ) ;
}
spin_unlock ( & cr - > conn_recovery_cmd_lock ) ;
spin_lock ( & sess - > cr_i_lock ) ;
kfree ( cr ) ;
}
spin_unlock ( & sess - > cr_i_lock ) ;
}
int iscsit_remove_active_connection_recovery_entry (
struct iscsi_conn_recovery * cr ,
struct iscsi_session * sess )
{
spin_lock ( & sess - > cr_a_lock ) ;
list_del ( & cr - > cr_list ) ;
sess - > conn_recovery_count - - ;
pr_debug ( " Decremented connection recovery count to %u for "
" SID: %u \n " , sess - > conn_recovery_count , sess - > sid ) ;
spin_unlock ( & sess - > cr_a_lock ) ;
kfree ( cr ) ;
return 0 ;
}
2012-09-26 16:00:36 +04:00
static void iscsit_remove_inactive_connection_recovery_entry (
2011-07-23 10:43:04 +04:00
struct iscsi_conn_recovery * cr ,
struct iscsi_session * sess )
{
spin_lock ( & sess - > cr_i_lock ) ;
list_del ( & cr - > cr_list ) ;
spin_unlock ( & sess - > cr_i_lock ) ;
}
/*
* Called with cr - > conn_recovery_cmd_lock help .
*/
int iscsit_remove_cmd_from_connection_recovery (
struct iscsi_cmd * cmd ,
struct iscsi_session * sess )
{
struct iscsi_conn_recovery * cr ;
if ( ! cmd - > cr ) {
pr_err ( " struct iscsi_conn_recovery pointer for ITT: 0x%08x "
" is NULL! \n " , cmd - > init_task_tag ) ;
BUG ( ) ;
}
cr = cmd - > cr ;
2012-04-04 02:51:01 +04:00
list_del ( & cmd - > i_conn_node ) ;
2011-07-23 10:43:04 +04:00
return - - cr - > cmd_count ;
}
void iscsit_discard_cr_cmds_by_expstatsn (
struct iscsi_conn_recovery * cr ,
u32 exp_statsn )
{
u32 dropped_count = 0 ;
struct iscsi_cmd * cmd , * cmd_tmp ;
struct iscsi_session * sess = cr - > sess ;
spin_lock ( & cr - > conn_recovery_cmd_lock ) ;
list_for_each_entry_safe ( cmd , cmd_tmp ,
2012-04-04 02:51:01 +04:00
& cr - > conn_recovery_cmd_list , i_conn_node ) {
2011-07-23 10:43:04 +04:00
if ( ( ( cmd - > deferred_i_state ! = ISTATE_SENT_STATUS ) & &
( cmd - > deferred_i_state ! = ISTATE_REMOVE ) ) | |
( cmd - > stat_sn > = exp_statsn ) ) {
continue ;
}
dropped_count + + ;
pr_debug ( " Dropping Acknowledged ITT: 0x%08x, StatSN: "
" 0x%08x, CID: %hu. \n " , cmd - > init_task_tag ,
cmd - > stat_sn , cr - > cid ) ;
iscsit_remove_cmd_from_connection_recovery ( cmd , sess ) ;
spin_unlock ( & cr - > conn_recovery_cmd_lock ) ;
2011-10-09 12:48:14 +04:00
iscsit_free_cmd ( cmd ) ;
2011-07-23 10:43:04 +04:00
spin_lock ( & cr - > conn_recovery_cmd_lock ) ;
}
spin_unlock ( & cr - > conn_recovery_cmd_lock ) ;
pr_debug ( " Dropped %u total acknowledged commands on "
" CID: %hu less than old ExpStatSN: 0x%08x \n " ,
dropped_count , cr - > cid , exp_statsn ) ;
if ( ! cr - > cmd_count ) {
pr_debug ( " No commands to be reassigned for failed "
" connection CID: %hu on SID: %u \n " ,
cr - > cid , sess - > sid ) ;
iscsit_remove_inactive_connection_recovery_entry ( cr , sess ) ;
iscsit_attach_active_connection_recovery_entry ( sess , cr ) ;
pr_debug ( " iSCSI connection recovery successful for CID: "
" %hu on SID: %u \n " , cr - > cid , sess - > sid ) ;
iscsit_remove_active_connection_recovery_entry ( cr , sess ) ;
} else {
iscsit_remove_inactive_connection_recovery_entry ( cr , sess ) ;
iscsit_attach_active_connection_recovery_entry ( sess , cr ) ;
}
}
int iscsit_discard_unacknowledged_ooo_cmdsns_for_conn ( struct iscsi_conn * conn )
{
u32 dropped_count = 0 ;
struct iscsi_cmd * cmd , * cmd_tmp ;
struct iscsi_ooo_cmdsn * ooo_cmdsn , * ooo_cmdsn_tmp ;
struct iscsi_session * sess = conn - > sess ;
mutex_lock ( & sess - > cmdsn_mutex ) ;
list_for_each_entry_safe ( ooo_cmdsn , ooo_cmdsn_tmp ,
& sess - > sess_ooo_cmdsn_list , ooo_list ) {
if ( ooo_cmdsn - > cid ! = conn - > cid )
continue ;
dropped_count + + ;
pr_debug ( " Dropping unacknowledged CmdSN: "
" 0x%08x during connection recovery on CID: %hu \n " ,
ooo_cmdsn - > cmdsn , conn - > cid ) ;
iscsit_remove_ooo_cmdsn ( sess , ooo_cmdsn ) ;
}
mutex_unlock ( & sess - > cmdsn_mutex ) ;
spin_lock_bh ( & conn - > cmd_lock ) ;
2012-04-04 02:51:01 +04:00
list_for_each_entry_safe ( cmd , cmd_tmp , & conn - > conn_cmd_list , i_conn_node ) {
2011-07-23 10:43:04 +04:00
if ( ! ( cmd - > cmd_flags & ICF_OOO_CMDSN ) )
continue ;
2012-04-04 02:51:01 +04:00
list_del ( & cmd - > i_conn_node ) ;
2011-07-23 10:43:04 +04:00
spin_unlock_bh ( & conn - > cmd_lock ) ;
2011-10-09 12:48:14 +04:00
iscsit_free_cmd ( cmd ) ;
2011-07-23 10:43:04 +04:00
spin_lock_bh ( & conn - > cmd_lock ) ;
}
spin_unlock_bh ( & conn - > cmd_lock ) ;
pr_debug ( " Dropped %u total unacknowledged commands on CID: "
" %hu for ExpCmdSN: 0x%08x. \n " , dropped_count , conn - > cid ,
sess - > exp_cmd_sn ) ;
return 0 ;
}
int iscsit_prepare_cmds_for_realligance ( struct iscsi_conn * conn )
{
u32 cmd_count = 0 ;
struct iscsi_cmd * cmd , * cmd_tmp ;
struct iscsi_conn_recovery * cr ;
/*
* Allocate an struct iscsi_conn_recovery for this connection .
* Each struct iscsi_cmd contains an struct iscsi_conn_recovery pointer
* ( struct iscsi_cmd - > cr ) so we need to allocate this before preparing the
* connection ' s command list for connection recovery .
*/
cr = kzalloc ( sizeof ( struct iscsi_conn_recovery ) , GFP_KERNEL ) ;
if ( ! cr ) {
pr_err ( " Unable to allocate memory for "
" struct iscsi_conn_recovery. \n " ) ;
return - 1 ;
}
INIT_LIST_HEAD ( & cr - > cr_list ) ;
INIT_LIST_HEAD ( & cr - > conn_recovery_cmd_list ) ;
spin_lock_init ( & cr - > conn_recovery_cmd_lock ) ;
/*
* Only perform connection recovery on ISCSI_OP_SCSI_CMD or
* ISCSI_OP_NOOP_OUT opcodes . For all other opcodes call
2012-04-04 02:51:01 +04:00
* list_del ( & cmd - > i_conn_node ) ; to release the command to the
2011-07-23 10:43:04 +04:00
* session pool and remove it from the connection ' s list .
*
* Also stop the DataOUT timer , which will be restarted after
* sending the TMR response .
*/
spin_lock_bh ( & conn - > cmd_lock ) ;
2012-04-04 02:51:01 +04:00
list_for_each_entry_safe ( cmd , cmd_tmp , & conn - > conn_cmd_list , i_conn_node ) {
2011-07-23 10:43:04 +04:00
if ( ( cmd - > iscsi_opcode ! = ISCSI_OP_SCSI_CMD ) & &
( cmd - > iscsi_opcode ! = ISCSI_OP_NOOP_OUT ) ) {
pr_debug ( " Not performing realligence on "
" Opcode: 0x%02x, ITT: 0x%08x, CmdSN: 0x%08x, "
" CID: %hu \n " , cmd - > iscsi_opcode ,
cmd - > init_task_tag , cmd - > cmd_sn , conn - > cid ) ;
2012-04-04 02:51:01 +04:00
list_del ( & cmd - > i_conn_node ) ;
2011-07-23 10:43:04 +04:00
spin_unlock_bh ( & conn - > cmd_lock ) ;
2011-10-09 12:48:14 +04:00
iscsit_free_cmd ( cmd ) ;
2011-07-23 10:43:04 +04:00
spin_lock_bh ( & conn - > cmd_lock ) ;
continue ;
}
/*
* Special case where commands greater than or equal to
* the session ' s ExpCmdSN are attached to the connection
* list but not to the out of order CmdSN list . The one
* obvious case is when a command with immediate data
* attached must only check the CmdSN against ExpCmdSN
* after the data is received . The special case below
* is when the connection fails before data is received ,
* but also may apply to other PDUs , so it has been
* made generic here .
*/
if ( ! ( cmd - > cmd_flags & ICF_OOO_CMDSN ) & & ! cmd - > immediate_cmd & &
2012-11-06 06:02:41 +04:00
iscsi_sna_gte ( cmd - > stat_sn , conn - > sess - > exp_cmd_sn ) ) {
2012-04-04 02:51:01 +04:00
list_del ( & cmd - > i_conn_node ) ;
2011-07-23 10:43:04 +04:00
spin_unlock_bh ( & conn - > cmd_lock ) ;
2011-10-09 12:48:14 +04:00
iscsit_free_cmd ( cmd ) ;
2011-07-23 10:43:04 +04:00
spin_lock_bh ( & conn - > cmd_lock ) ;
continue ;
}
cmd_count + + ;
pr_debug ( " Preparing Opcode: 0x%02x, ITT: 0x%08x, "
" CmdSN: 0x%08x, StatSN: 0x%08x, CID: %hu for "
" realligence. \n " , cmd - > iscsi_opcode ,
cmd - > init_task_tag , cmd - > cmd_sn , cmd - > stat_sn ,
conn - > cid ) ;
cmd - > deferred_i_state = cmd - > i_state ;
cmd - > i_state = ISTATE_IN_CONNECTION_RECOVERY ;
if ( cmd - > data_direction = = DMA_TO_DEVICE )
iscsit_stop_dataout_timer ( cmd ) ;
cmd - > sess = conn - > sess ;
2012-04-04 02:51:01 +04:00
list_del ( & cmd - > i_conn_node ) ;
2011-07-23 10:43:04 +04:00
spin_unlock_bh ( & conn - > cmd_lock ) ;
iscsit_free_all_datain_reqs ( cmd ) ;
2011-10-09 12:48:14 +04:00
transport_wait_for_tasks ( & cmd - > se_cmd ) ;
2011-07-23 10:43:04 +04:00
/*
* Add the struct iscsi_cmd to the connection recovery cmd list
*/
spin_lock ( & cr - > conn_recovery_cmd_lock ) ;
2012-04-04 02:51:01 +04:00
list_add_tail ( & cmd - > i_conn_node , & cr - > conn_recovery_cmd_list ) ;
2011-07-23 10:43:04 +04:00
spin_unlock ( & cr - > conn_recovery_cmd_lock ) ;
spin_lock_bh ( & conn - > cmd_lock ) ;
cmd - > cr = cr ;
cmd - > conn = NULL ;
}
spin_unlock_bh ( & conn - > cmd_lock ) ;
/*
* Fill in the various values in the preallocated struct iscsi_conn_recovery .
*/
cr - > cid = conn - > cid ;
cr - > cmd_count = cmd_count ;
cr - > maxrecvdatasegmentlength = conn - > conn_ops - > MaxRecvDataSegmentLength ;
2012-09-30 09:14:51 +04:00
cr - > maxxmitdatasegmentlength = conn - > conn_ops - > MaxXmitDataSegmentLength ;
2011-07-23 10:43:04 +04:00
cr - > sess = conn - > sess ;
iscsit_attach_inactive_connection_recovery_entry ( conn - > sess , cr ) ;
return 0 ;
}
int iscsit_connection_recovery_transport_reset ( struct iscsi_conn * conn )
{
atomic_set ( & conn - > connection_recovery , 1 ) ;
if ( iscsit_close_connection ( conn ) < 0 )
return - 1 ;
return 0 ;
}