mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-06 02:50:43 +00:00
![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 3 normalized pattern(s): 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 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 [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] 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 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 [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
429 lines
12 KiB
C
429 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*******************************************************************************
|
|
* This file contains error recovery level two functions used by
|
|
* the iSCSI Target driver.
|
|
*
|
|
* (c) Copyright 2007-2013 Datera, Inc.
|
|
*
|
|
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
|
|
*
|
|
******************************************************************************/
|
|
|
|
#include <linux/slab.h>
|
|
#include <scsi/iscsi_proto.h>
|
|
#include <target/target_core_base.h>
|
|
#include <target/target_core_fabric.h>
|
|
|
|
#include <target/iscsi/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,
|
|
__be32 exp_data_sn)
|
|
{
|
|
u32 data_sn = 0;
|
|
struct iscsi_conn *conn = cmd->conn;
|
|
|
|
cmd->next_burst_len = 0;
|
|
cmd->read_data_done = 0;
|
|
|
|
while (be32_to_cpu(exp_data_sn) > data_sn) {
|
|
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_entries(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,
|
|
&cr->conn_recovery_cmd_list, i_conn_node) {
|
|
|
|
list_del_init(&cmd->i_conn_node);
|
|
cmd->conn = NULL;
|
|
spin_unlock(&cr->conn_recovery_cmd_lock);
|
|
iscsit_free_cmd(cmd, true);
|
|
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,
|
|
&cr->conn_recovery_cmd_list, i_conn_node) {
|
|
|
|
list_del_init(&cmd->i_conn_node);
|
|
cmd->conn = NULL;
|
|
spin_unlock(&cr->conn_recovery_cmd_lock);
|
|
iscsit_free_cmd(cmd, true);
|
|
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;
|
|
}
|
|
|
|
static void iscsit_remove_inactive_connection_recovery_entry(
|
|
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;
|
|
|
|
list_del_init(&cmd->i_conn_node);
|
|
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,
|
|
&cr->conn_recovery_cmd_list, i_conn_node) {
|
|
|
|
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);
|
|
iscsit_free_cmd(cmd, true);
|
|
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);
|
|
list_for_each_entry_safe(cmd, cmd_tmp, &conn->conn_cmd_list, i_conn_node) {
|
|
if (!(cmd->cmd_flags & ICF_OOO_CMDSN))
|
|
continue;
|
|
|
|
list_del_init(&cmd->i_conn_node);
|
|
|
|
spin_unlock_bh(&conn->cmd_lock);
|
|
iscsit_free_cmd(cmd, true);
|
|
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_reallegiance(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
|
|
* list_del_init(&cmd->i_conn_node); to release the command to the
|
|
* 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);
|
|
list_for_each_entry_safe(cmd, cmd_tmp, &conn->conn_cmd_list, i_conn_node) {
|
|
|
|
if ((cmd->iscsi_opcode != ISCSI_OP_SCSI_CMD) &&
|
|
(cmd->iscsi_opcode != ISCSI_OP_NOOP_OUT)) {
|
|
pr_debug("Not performing reallegiance 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);
|
|
|
|
list_del_init(&cmd->i_conn_node);
|
|
spin_unlock_bh(&conn->cmd_lock);
|
|
iscsit_free_cmd(cmd, true);
|
|
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 &&
|
|
iscsi_sna_gte(cmd->cmd_sn, conn->sess->exp_cmd_sn)) {
|
|
list_del_init(&cmd->i_conn_node);
|
|
spin_unlock_bh(&conn->cmd_lock);
|
|
iscsit_free_cmd(cmd, true);
|
|
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"
|
|
" reallegiance.\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;
|
|
|
|
list_del_init(&cmd->i_conn_node);
|
|
spin_unlock_bh(&conn->cmd_lock);
|
|
|
|
iscsit_free_all_datain_reqs(cmd);
|
|
|
|
transport_wait_for_tasks(&cmd->se_cmd);
|
|
/*
|
|
* Add the struct iscsi_cmd to the connection recovery cmd list
|
|
*/
|
|
spin_lock(&cr->conn_recovery_cmd_lock);
|
|
list_add_tail(&cmd->i_conn_node, &cr->conn_recovery_cmd_list);
|
|
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;
|
|
cr->maxxmitdatasegmentlength = conn->conn_ops->MaxXmitDataSegmentLength;
|
|
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;
|
|
}
|