root/drivers/target/iscsi/iscsi_target_erl1.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*******************************************************************************
 * This file contains error recovery level one used by the iSCSI Target driver.
 *
 * (c) Copyright 2007-2013 Datera, Inc.
 *
 * Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
 *
 ******************************************************************************/

#include <linux/list.h>
#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_transport.h>

#include <target/iscsi/iscsi_target_core.h>
#include "iscsi_target_seq_pdu_list.h"
#include "iscsi_target_datain_values.h"
#include "iscsi_target_device.h"
#include "iscsi_target_tpg.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"

#define OFFLOAD_BUF_SIZE        32768U

/*
 *      Used to dump excess datain payload for certain error recovery
 *      situations.  Receive in OFFLOAD_BUF_SIZE max of datain per rx_data().
 *
 *      dump_padding_digest denotes if padding and data digests need
 *      to be dumped.
 */
int iscsit_dump_data_payload(
        struct iscsit_conn *conn,
        u32 buf_len,
        int dump_padding_digest)
{
        char *buf;
        int ret = DATAOUT_WITHIN_COMMAND_RECOVERY, rx_got;
        u32 length, offset = 0, size;
        struct kvec iov;

        if (conn->sess->sess_ops->RDMAExtensions)
                return 0;

        if (dump_padding_digest) {
                buf_len = ALIGN(buf_len, 4);
                if (conn->conn_ops->DataDigest)
                        buf_len += ISCSI_CRC_LEN;
        }

        length = min(buf_len, OFFLOAD_BUF_SIZE);

        buf = kzalloc(length, GFP_ATOMIC);
        if (!buf) {
                pr_err("Unable to allocate %u bytes for offload"
                                " buffer.\n", length);
                return -1;
        }
        memset(&iov, 0, sizeof(struct kvec));

        while (offset < buf_len) {
                size = min(buf_len - offset, length);

                iov.iov_len = size;
                iov.iov_base = buf;

                rx_got = rx_data(conn, &iov, 1, size);
                if (rx_got != size) {
                        ret = DATAOUT_CANNOT_RECOVER;
                        break;
                }

                offset += size;
        }

        kfree(buf);
        return ret;
}

/*
 *      Used for retransmitting R2Ts from a R2T SNACK request.
 */
static int iscsit_send_recovery_r2t_for_snack(
        struct iscsit_cmd *cmd,
        struct iscsi_r2t *r2t)
{
        /*
         * If the struct iscsi_r2t has not been sent yet, we can safely
         * ignore retransmission
         * of the R2TSN in question.
         */
        spin_lock_bh(&cmd->r2t_lock);
        if (!r2t->sent_r2t) {
                spin_unlock_bh(&cmd->r2t_lock);
                return 0;
        }
        r2t->sent_r2t = 0;
        spin_unlock_bh(&cmd->r2t_lock);

        iscsit_add_cmd_to_immediate_queue(cmd, cmd->conn, ISTATE_SEND_R2T);

        return 0;
}

static int iscsit_handle_r2t_snack(
        struct iscsit_cmd *cmd,
        unsigned char *buf,
        u32 begrun,
        u32 runlength)
{
        u32 last_r2tsn;
        struct iscsi_r2t *r2t;

        /*
         * Make sure the initiator is not requesting retransmission
         * of R2TSNs already acknowledged by a TMR TASK_REASSIGN.
         */
        if ((cmd->cmd_flags & ICF_GOT_DATACK_SNACK) &&
            (begrun <= cmd->acked_data_sn)) {
                pr_err("ITT: 0x%08x, R2T SNACK requesting"
                        " retransmission of R2TSN: 0x%08x to 0x%08x but already"
                        " acked to  R2TSN: 0x%08x by TMR TASK_REASSIGN,"
                        " protocol error.\n", cmd->init_task_tag, begrun,
                        (begrun + runlength), cmd->acked_data_sn);

                return iscsit_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR, buf);
        }

        if (runlength) {
                if ((begrun + runlength) > cmd->r2t_sn) {
                        pr_err("Command ITT: 0x%08x received R2T SNACK"
                        " with BegRun: 0x%08x, RunLength: 0x%08x, exceeds"
                        " current R2TSN: 0x%08x, protocol error.\n",
                        cmd->init_task_tag, begrun, runlength, cmd->r2t_sn);
                        return iscsit_reject_cmd(cmd,
                                        ISCSI_REASON_BOOKMARK_INVALID, buf);
                }
                last_r2tsn = (begrun + runlength);
        } else
                last_r2tsn = cmd->r2t_sn;

        while (begrun < last_r2tsn) {
                r2t = iscsit_get_holder_for_r2tsn(cmd, begrun);
                if (!r2t)
                        return -1;
                if (iscsit_send_recovery_r2t_for_snack(cmd, r2t) < 0)
                        return -1;

                begrun++;
        }

        return 0;
}

/*
 *      Generates Offsets and NextBurstLength based on Begrun and Runlength
 *      carried in a Data SNACK or ExpDataSN in TMR TASK_REASSIGN.
 *
 *      For DataSequenceInOrder=Yes and DataPDUInOrder=[Yes,No] only.
 *
 *      FIXME: How is this handled for a RData SNACK?
 */
int iscsit_create_recovery_datain_values_datasequenceinorder_yes(
        struct iscsit_cmd *cmd,
        struct iscsi_datain_req *dr)
{
        u32 data_sn = 0, data_sn_count = 0;
        u32 pdu_start = 0, seq_no = 0;
        u32 begrun = dr->begrun;
        struct iscsit_conn *conn = cmd->conn;

        while (begrun > data_sn++) {
                data_sn_count++;
                if ((dr->next_burst_len +
                     conn->conn_ops->MaxRecvDataSegmentLength) <
                     conn->sess->sess_ops->MaxBurstLength) {
                        dr->read_data_done +=
                                conn->conn_ops->MaxRecvDataSegmentLength;
                        dr->next_burst_len +=
                                conn->conn_ops->MaxRecvDataSegmentLength;
                } else {
                        dr->read_data_done +=
                                (conn->sess->sess_ops->MaxBurstLength -
                                 dr->next_burst_len);
                        dr->next_burst_len = 0;
                        pdu_start += data_sn_count;
                        data_sn_count = 0;
                        seq_no++;
                }
        }

        if (!conn->sess->sess_ops->DataPDUInOrder) {
                cmd->seq_no = seq_no;
                cmd->pdu_start = pdu_start;
                cmd->pdu_send_order = data_sn_count;
        }

        return 0;
}

/*
 *      Generates Offsets and NextBurstLength based on Begrun and Runlength
 *      carried in a Data SNACK or ExpDataSN in TMR TASK_REASSIGN.
 *
 *      For DataSequenceInOrder=No and DataPDUInOrder=[Yes,No] only.
 *
 *      FIXME: How is this handled for a RData SNACK?
 */
int iscsit_create_recovery_datain_values_datasequenceinorder_no(
        struct iscsit_cmd *cmd,
        struct iscsi_datain_req *dr)
{
        int found_seq = 0, i;
        u32 data_sn, read_data_done = 0, seq_send_order = 0;
        u32 begrun = dr->begrun;
        u32 runlength = dr->runlength;
        struct iscsit_conn *conn = cmd->conn;
        struct iscsi_seq *first_seq = NULL, *seq = NULL;

        if (!cmd->seq_list) {
                pr_err("struct iscsit_cmd->seq_list is NULL!\n");
                return -1;
        }

        /*
         * Calculate read_data_done for all sequences containing a
         * first_datasn and last_datasn less than the BegRun.
         *
         * Locate the struct iscsi_seq the BegRun lies within and calculate
         * NextBurstLenghth up to the DataSN based on MaxRecvDataSegmentLength.
         *
         * Also use struct iscsi_seq->seq_send_order to determine where to start.
         */
        for (i = 0; i < cmd->seq_count; i++) {
                seq = &cmd->seq_list[i];

                if (!seq->seq_send_order)
                        first_seq = seq;

                /*
                 * No data has been transferred for this DataIN sequence, so the
                 * seq->first_datasn and seq->last_datasn have not been set.
                 */
                if (!seq->sent) {
                        pr_err("Ignoring non-sent sequence 0x%08x ->"
                                " 0x%08x\n\n", seq->first_datasn,
                                seq->last_datasn);
                        continue;
                }

                /*
                 * This DataIN sequence is precedes the received BegRun, add the
                 * total xfer_len of the sequence to read_data_done and reset
                 * seq->pdu_send_order.
                 */
                if ((seq->first_datasn < begrun) &&
                                (seq->last_datasn < begrun)) {
                        pr_err("Pre BegRun sequence 0x%08x ->"
                                " 0x%08x\n", seq->first_datasn,
                                seq->last_datasn);

                        read_data_done += cmd->seq_list[i].xfer_len;
                        seq->next_burst_len = seq->pdu_send_order = 0;
                        continue;
                }

                /*
                 * The BegRun lies within this DataIN sequence.
                 */
                if ((seq->first_datasn <= begrun) &&
                                (seq->last_datasn >= begrun)) {
                        pr_err("Found sequence begrun: 0x%08x in"
                                " 0x%08x -> 0x%08x\n", begrun,
                                seq->first_datasn, seq->last_datasn);

                        seq_send_order = seq->seq_send_order;
                        data_sn = seq->first_datasn;
                        seq->next_burst_len = seq->pdu_send_order = 0;
                        found_seq = 1;

                        /*
                         * For DataPDUInOrder=Yes, while the first DataSN of
                         * the sequence is less than the received BegRun, add
                         * the MaxRecvDataSegmentLength to read_data_done and
                         * to the sequence's next_burst_len;
                         *
                         * For DataPDUInOrder=No, while the first DataSN of the
                         * sequence is less than the received BegRun, find the
                         * struct iscsi_pdu of the DataSN in question and add the
                         * MaxRecvDataSegmentLength to read_data_done and to the
                         * sequence's next_burst_len;
                         */
                        if (conn->sess->sess_ops->DataPDUInOrder) {
                                while (data_sn < begrun) {
                                        seq->pdu_send_order++;
                                        read_data_done +=
                                                conn->conn_ops->MaxRecvDataSegmentLength;
                                        seq->next_burst_len +=
                                                conn->conn_ops->MaxRecvDataSegmentLength;
                                        data_sn++;
                                }
                        } else {
                                int j;
                                struct iscsi_pdu *pdu;

                                while (data_sn < begrun) {
                                        seq->pdu_send_order++;

                                        for (j = 0; j < seq->pdu_count; j++) {
                                                pdu = &cmd->pdu_list[
                                                        seq->pdu_start + j];
                                                if (pdu->data_sn == data_sn) {
                                                        read_data_done +=
                                                                pdu->length;
                                                        seq->next_burst_len +=
                                                                pdu->length;
                                                }
                                        }
                                        data_sn++;
                                }
                        }
                        continue;
                }

                /*
                 * This DataIN sequence is larger than the received BegRun,
                 * reset seq->pdu_send_order and continue.
                 */
                if ((seq->first_datasn > begrun) ||
                                (seq->last_datasn > begrun)) {
                        pr_err("Post BegRun sequence 0x%08x -> 0x%08x\n",
                                        seq->first_datasn, seq->last_datasn);

                        seq->next_burst_len = seq->pdu_send_order = 0;
                        continue;
                }
        }

        if (!found_seq) {
                if (!begrun) {
                        if (!first_seq) {
                                pr_err("ITT: 0x%08x, Begrun: 0x%08x"
                                        " but first_seq is NULL\n",
                                        cmd->init_task_tag, begrun);
                                return -1;
                        }
                        seq_send_order = first_seq->seq_send_order;
                        seq->next_burst_len = seq->pdu_send_order = 0;
                        goto done;
                }

                pr_err("Unable to locate struct iscsi_seq for ITT: 0x%08x,"
                        " BegRun: 0x%08x, RunLength: 0x%08x while"
                        " DataSequenceInOrder=No and DataPDUInOrder=%s.\n",
                                cmd->init_task_tag, begrun, runlength,
                        (conn->sess->sess_ops->DataPDUInOrder) ? "Yes" : "No");
                return -1;
        }

done:
        dr->read_data_done = read_data_done;
        dr->seq_send_order = seq_send_order;

        return 0;
}

static int iscsit_handle_recovery_datain(
        struct iscsit_cmd *cmd,
        unsigned char *buf,
        u32 begrun,
        u32 runlength)
{
        struct iscsit_conn *conn = cmd->conn;
        struct iscsi_datain_req *dr;
        struct se_cmd *se_cmd = &cmd->se_cmd;

        if (!(se_cmd->transport_state & CMD_T_COMPLETE)) {
                pr_err("Ignoring ITT: 0x%08x Data SNACK\n",
                                cmd->init_task_tag);
                return 0;
        }

        /*
         * Make sure the initiator is not requesting retransmission
         * of DataSNs already acknowledged by a Data ACK SNACK.
         */
        if ((cmd->cmd_flags & ICF_GOT_DATACK_SNACK) &&
            (begrun <= cmd->acked_data_sn)) {
                pr_err("ITT: 0x%08x, Data SNACK requesting"
                        " retransmission of DataSN: 0x%08x to 0x%08x but"
                        " already acked to DataSN: 0x%08x by Data ACK SNACK,"
                        " protocol error.\n", cmd->init_task_tag, begrun,
                        (begrun + runlength), cmd->acked_data_sn);

                return iscsit_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR, buf);
        }

        /*
         * Make sure BegRun and RunLength in the Data SNACK are sane.
         * Note: (cmd->data_sn - 1) will carry the maximum DataSN sent.
         */
        if ((begrun + runlength) > (cmd->data_sn - 1)) {
                pr_err("Initiator requesting BegRun: 0x%08x, RunLength"
                        ": 0x%08x greater than maximum DataSN: 0x%08x.\n",
                                begrun, runlength, (cmd->data_sn - 1));
                return iscsit_reject_cmd(cmd, ISCSI_REASON_BOOKMARK_INVALID,
                                         buf);
        }

        dr = iscsit_allocate_datain_req();
        if (!dr)
                return iscsit_reject_cmd(cmd, ISCSI_REASON_BOOKMARK_NO_RESOURCES,
                                         buf);

        dr->data_sn = dr->begrun = begrun;
        dr->runlength = runlength;
        dr->generate_recovery_values = 1;
        dr->recovery = DATAIN_WITHIN_COMMAND_RECOVERY;

        iscsit_attach_datain_req(cmd, dr);

        cmd->i_state = ISTATE_SEND_DATAIN;
        iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);

        return 0;
}

int iscsit_handle_recovery_datain_or_r2t(
        struct iscsit_conn *conn,
        unsigned char *buf,
        itt_t init_task_tag,
        u32 targ_xfer_tag,
        u32 begrun,
        u32 runlength)
{
        struct iscsit_cmd *cmd;

        cmd = iscsit_find_cmd_from_itt(conn, init_task_tag);
        if (!cmd)
                return 0;

        /*
         * FIXME: This will not work for bidi commands.
         */
        switch (cmd->data_direction) {
        case DMA_TO_DEVICE:
                return iscsit_handle_r2t_snack(cmd, buf, begrun, runlength);
        case DMA_FROM_DEVICE:
                return iscsit_handle_recovery_datain(cmd, buf, begrun,
                                runlength);
        default:
                pr_err("Unknown cmd->data_direction: 0x%02x\n",
                                cmd->data_direction);
                return -1;
        }

        return 0;
}

/* #warning FIXME: Status SNACK needs to be dependent on OPCODE!!! */
int iscsit_handle_status_snack(
        struct iscsit_conn *conn,
        itt_t init_task_tag,
        u32 targ_xfer_tag,
        u32 begrun,
        u32 runlength)
{
        struct iscsit_cmd *cmd = NULL;
        u32 last_statsn;
        int found_cmd;

        if (!begrun) {
                begrun = conn->exp_statsn;
        } else if (conn->exp_statsn > begrun) {
                pr_err("Got Status SNACK Begrun: 0x%08x, RunLength:"
                        " 0x%08x but already got ExpStatSN: 0x%08x on CID:"
                        " %hu.\n", begrun, runlength, conn->exp_statsn,
                        conn->cid);
                return 0;
        }

        last_statsn = (!runlength) ? conn->stat_sn : (begrun + runlength);

        while (begrun < last_statsn) {
                found_cmd = 0;

                spin_lock_bh(&conn->cmd_lock);
                list_for_each_entry(cmd, &conn->conn_cmd_list, i_conn_node) {
                        if (cmd->stat_sn == begrun) {
                                found_cmd = 1;
                                break;
                        }
                }
                spin_unlock_bh(&conn->cmd_lock);

                if (!found_cmd) {
                        pr_err("Unable to find StatSN: 0x%08x for"
                                " a Status SNACK, assuming this was a"
                                " protactic SNACK for an untransmitted"
                                " StatSN, ignoring.\n", begrun);
                        begrun++;
                        continue;
                }

                spin_lock_bh(&cmd->istate_lock);
                if (cmd->i_state == ISTATE_SEND_DATAIN) {
                        spin_unlock_bh(&cmd->istate_lock);
                        pr_err("Ignoring Status SNACK for BegRun:"
                                " 0x%08x, RunLength: 0x%08x, assuming this was"
                                " a protactic SNACK for an untransmitted"
                                " StatSN\n", begrun, runlength);
                        begrun++;
                        continue;
                }
                spin_unlock_bh(&cmd->istate_lock);

                cmd->i_state = ISTATE_SEND_STATUS_RECOVERY;
                iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
                begrun++;
        }

        return 0;
}

int iscsit_handle_data_ack(
        struct iscsit_conn *conn,
        u32 targ_xfer_tag,
        u32 begrun,
        u32 runlength)
{
        struct iscsit_cmd *cmd = NULL;

        cmd = iscsit_find_cmd_from_ttt(conn, targ_xfer_tag);
        if (!cmd) {
                pr_err("Data ACK SNACK for TTT: 0x%08x is"
                        " invalid.\n", targ_xfer_tag);
                return -1;
        }

        if (begrun <= cmd->acked_data_sn) {
                pr_err("ITT: 0x%08x Data ACK SNACK BegRUN: 0x%08x is"
                        " less than the already acked DataSN: 0x%08x.\n",
                        cmd->init_task_tag, begrun, cmd->acked_data_sn);
                return -1;
        }

        /*
         * For Data ACK SNACK, BegRun is the next expected DataSN.
         * (see iSCSI v19: 10.16.6)
         */
        cmd->cmd_flags |= ICF_GOT_DATACK_SNACK;
        cmd->acked_data_sn = (begrun - 1);

        pr_debug("Received Data ACK SNACK for ITT: 0x%08x,"
                " updated acked DataSN to 0x%08x.\n",
                        cmd->init_task_tag, cmd->acked_data_sn);

        return 0;
}

static int iscsit_send_recovery_r2t(
        struct iscsit_cmd *cmd,
        u32 offset,
        u32 xfer_len)
{
        int ret;

        spin_lock_bh(&cmd->r2t_lock);
        ret = iscsit_add_r2t_to_list(cmd, offset, xfer_len, 1, 0);
        spin_unlock_bh(&cmd->r2t_lock);

        return ret;
}

int iscsit_dataout_datapduinorder_no_fbit(
        struct iscsit_cmd *cmd,
        struct iscsi_pdu *pdu)
{
        int i, send_recovery_r2t = 0, recovery = 0;
        u32 length = 0, offset = 0, pdu_count = 0;
        struct iscsit_conn *conn = cmd->conn;
        struct iscsi_pdu *first_pdu = NULL;

        /*
         * Get an struct iscsi_pdu pointer to the first PDU, and total PDU count
         * of the DataOUT sequence.
         */
        if (conn->sess->sess_ops->DataSequenceInOrder) {
                for (i = 0; i < cmd->pdu_count; i++) {
                        if (cmd->pdu_list[i].seq_no == pdu->seq_no) {
                                if (!first_pdu)
                                        first_pdu = &cmd->pdu_list[i];
                                pdu_count++;
                        } else if (pdu_count)
                                break;
                }
        } else {
                struct iscsi_seq *seq = cmd->seq_ptr;

                first_pdu = &cmd->pdu_list[seq->pdu_start];
                pdu_count = seq->pdu_count;
        }

        if (!first_pdu || !pdu_count)
                return DATAOUT_CANNOT_RECOVER;

        /*
         * Loop through the ending DataOUT Sequence checking each struct iscsi_pdu.
         * The following ugly logic does batching of not received PDUs.
         */
        for (i = 0; i < pdu_count; i++) {
                if (first_pdu[i].status == ISCSI_PDU_RECEIVED_OK) {
                        if (!send_recovery_r2t)
                                continue;

                        if (iscsit_send_recovery_r2t(cmd, offset, length) < 0)
                                return DATAOUT_CANNOT_RECOVER;

                        send_recovery_r2t = length = offset = 0;
                        continue;
                }
                /*
                 * Set recovery = 1 for any missing, CRC failed, or timed
                 * out PDUs to let the DataOUT logic know that this sequence
                 * has not been completed yet.
                 *
                 * Also, only send a Recovery R2T for ISCSI_PDU_NOT_RECEIVED.
                 * We assume if the PDU either failed CRC or timed out
                 * that a Recovery R2T has already been sent.
                 */
                recovery = 1;

                if (first_pdu[i].status != ISCSI_PDU_NOT_RECEIVED)
                        continue;

                if (!offset)
                        offset = first_pdu[i].offset;
                length += first_pdu[i].length;

                send_recovery_r2t = 1;
        }

        if (send_recovery_r2t)
                if (iscsit_send_recovery_r2t(cmd, offset, length) < 0)
                        return DATAOUT_CANNOT_RECOVER;

        return (!recovery) ? DATAOUT_NORMAL : DATAOUT_WITHIN_COMMAND_RECOVERY;
}

static int iscsit_recalculate_dataout_values(
        struct iscsit_cmd *cmd,
        u32 pdu_offset,
        u32 pdu_length,
        u32 *r2t_offset,
        u32 *r2t_length)
{
        int i;
        struct iscsit_conn *conn = cmd->conn;
        struct iscsi_pdu *pdu = NULL;

        if (conn->sess->sess_ops->DataSequenceInOrder) {
                cmd->data_sn = 0;

                if (conn->sess->sess_ops->DataPDUInOrder) {
                        *r2t_offset = cmd->write_data_done;
                        *r2t_length = (cmd->seq_end_offset -
                                        cmd->write_data_done);
                        return 0;
                }

                *r2t_offset = cmd->seq_start_offset;
                *r2t_length = (cmd->seq_end_offset - cmd->seq_start_offset);

                for (i = 0; i < cmd->pdu_count; i++) {
                        pdu = &cmd->pdu_list[i];

                        if (pdu->status != ISCSI_PDU_RECEIVED_OK)
                                continue;

                        if ((pdu->offset >= cmd->seq_start_offset) &&
                           ((pdu->offset + pdu->length) <=
                             cmd->seq_end_offset)) {
                                if (!cmd->unsolicited_data)
                                        cmd->next_burst_len -= pdu->length;
                                else
                                        cmd->first_burst_len -= pdu->length;

                                cmd->write_data_done -= pdu->length;
                                pdu->status = ISCSI_PDU_NOT_RECEIVED;
                        }
                }
        } else {
                struct iscsi_seq *seq = NULL;

                seq = iscsit_get_seq_holder(cmd, pdu_offset, pdu_length);
                if (!seq)
                        return -1;

                *r2t_offset = seq->orig_offset;
                *r2t_length = seq->xfer_len;

                cmd->write_data_done -= (seq->offset - seq->orig_offset);
                if (cmd->immediate_data)
                        cmd->first_burst_len = cmd->write_data_done;

                seq->data_sn = 0;
                seq->offset = seq->orig_offset;
                seq->next_burst_len = 0;
                seq->status = DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY;

                if (conn->sess->sess_ops->DataPDUInOrder)
                        return 0;

                for (i = 0; i < seq->pdu_count; i++) {
                        pdu = &cmd->pdu_list[i+seq->pdu_start];

                        if (pdu->status != ISCSI_PDU_RECEIVED_OK)
                                continue;

                        pdu->status = ISCSI_PDU_NOT_RECEIVED;
                }
        }

        return 0;
}

int iscsit_recover_dataout_sequence(
        struct iscsit_cmd *cmd,
        u32 pdu_offset,
        u32 pdu_length)
{
        u32 r2t_length = 0, r2t_offset = 0;

        spin_lock_bh(&cmd->istate_lock);
        cmd->cmd_flags |= ICF_WITHIN_COMMAND_RECOVERY;
        spin_unlock_bh(&cmd->istate_lock);

        if (iscsit_recalculate_dataout_values(cmd, pdu_offset, pdu_length,
                        &r2t_offset, &r2t_length) < 0)
                return DATAOUT_CANNOT_RECOVER;

        iscsit_send_recovery_r2t(cmd, r2t_offset, r2t_length);

        return DATAOUT_WITHIN_COMMAND_RECOVERY;
}

static struct iscsi_ooo_cmdsn *iscsit_allocate_ooo_cmdsn(void)
{
        struct iscsi_ooo_cmdsn *ooo_cmdsn = NULL;

        ooo_cmdsn = kmem_cache_zalloc(lio_ooo_cache, GFP_ATOMIC);
        if (!ooo_cmdsn) {
                pr_err("Unable to allocate memory for"
                        " struct iscsi_ooo_cmdsn.\n");
                return NULL;
        }
        INIT_LIST_HEAD(&ooo_cmdsn->ooo_list);

        return ooo_cmdsn;
}

static int iscsit_attach_ooo_cmdsn(
        struct iscsit_session *sess,
        struct iscsi_ooo_cmdsn *ooo_cmdsn)
{
        struct iscsi_ooo_cmdsn *ooo_tail, *ooo_tmp;

        lockdep_assert_held(&sess->cmdsn_mutex);

        /*
         * We attach the struct iscsi_ooo_cmdsn entry to the out of order
         * list in increasing CmdSN order.
         * This allows iscsi_execute_ooo_cmdsns() to detect any
         * additional CmdSN holes while performing delayed execution.
         */
        if (list_empty(&sess->sess_ooo_cmdsn_list))
                list_add_tail(&ooo_cmdsn->ooo_list,
                                &sess->sess_ooo_cmdsn_list);
        else {
                ooo_tail = list_entry(sess->sess_ooo_cmdsn_list.prev,
                                typeof(*ooo_tail), ooo_list);
                /*
                 * CmdSN is greater than the tail of the list.
                 */
                if (iscsi_sna_lt(ooo_tail->cmdsn, ooo_cmdsn->cmdsn))
                        list_add_tail(&ooo_cmdsn->ooo_list,
                                        &sess->sess_ooo_cmdsn_list);
                else {
                        /*
                         * CmdSN is either lower than the head,  or somewhere
                         * in the middle.
                         */
                        list_for_each_entry(ooo_tmp, &sess->sess_ooo_cmdsn_list,
                                                ooo_list) {
                                if (iscsi_sna_lt(ooo_tmp->cmdsn, ooo_cmdsn->cmdsn))
                                        continue;

                                /* Insert before this entry */
                                list_add(&ooo_cmdsn->ooo_list,
                                        ooo_tmp->ooo_list.prev);
                                break;
                        }
                }
        }

        return 0;
}

/*
 *      Removes an struct iscsi_ooo_cmdsn from a session's list,
 *      called with struct iscsit_session->cmdsn_mutex held.
 */
void iscsit_remove_ooo_cmdsn(
        struct iscsit_session *sess,
        struct iscsi_ooo_cmdsn *ooo_cmdsn)
{
        list_del(&ooo_cmdsn->ooo_list);
        kmem_cache_free(lio_ooo_cache, ooo_cmdsn);
}

void iscsit_clear_ooo_cmdsns_for_conn(struct iscsit_conn *conn)
{
        struct iscsi_ooo_cmdsn *ooo_cmdsn;
        struct iscsit_session *sess = conn->sess;

        mutex_lock(&sess->cmdsn_mutex);
        list_for_each_entry(ooo_cmdsn, &sess->sess_ooo_cmdsn_list, ooo_list) {
                if (ooo_cmdsn->cid != conn->cid)
                        continue;

                ooo_cmdsn->cmd = NULL;
        }
        mutex_unlock(&sess->cmdsn_mutex);
}

int iscsit_execute_ooo_cmdsns(struct iscsit_session *sess)
{
        int ooo_count = 0;
        struct iscsit_cmd *cmd = NULL;
        struct iscsi_ooo_cmdsn *ooo_cmdsn, *ooo_cmdsn_tmp;

        lockdep_assert_held(&sess->cmdsn_mutex);

        list_for_each_entry_safe(ooo_cmdsn, ooo_cmdsn_tmp,
                                &sess->sess_ooo_cmdsn_list, ooo_list) {
                if (ooo_cmdsn->cmdsn != sess->exp_cmd_sn)
                        continue;

                if (!ooo_cmdsn->cmd) {
                        sess->exp_cmd_sn++;
                        iscsit_remove_ooo_cmdsn(sess, ooo_cmdsn);
                        continue;
                }

                cmd = ooo_cmdsn->cmd;
                cmd->i_state = cmd->deferred_i_state;
                ooo_count++;
                sess->exp_cmd_sn++;
                pr_debug("Executing out of order CmdSN: 0x%08x,"
                        " incremented ExpCmdSN to 0x%08x.\n",
                        cmd->cmd_sn, sess->exp_cmd_sn);

                iscsit_remove_ooo_cmdsn(sess, ooo_cmdsn);

                if (iscsit_execute_cmd(cmd, 1) < 0)
                        return -1;
        }

        return ooo_count;
}

/*
 *      Called either:
 *
 *      1. With sess->cmdsn_mutex held from iscsi_execute_ooo_cmdsns()
 *      or iscsi_check_received_cmdsn().
 *      2. With no locks held directly from iscsi_handle_XXX_pdu() functions
 *      for immediate commands.
 */
int iscsit_execute_cmd(struct iscsit_cmd *cmd, int ooo)
{
        struct se_cmd *se_cmd = &cmd->se_cmd;
        struct iscsit_conn *conn = cmd->conn;
        int lr = 0;

        spin_lock_bh(&cmd->istate_lock);
        if (ooo)
                cmd->cmd_flags &= ~ICF_OOO_CMDSN;

        switch (cmd->iscsi_opcode) {
        case ISCSI_OP_SCSI_CMD:
                /*
                 * Go ahead and send the CHECK_CONDITION status for
                 * any SCSI CDB exceptions that may have occurred.
                 */
                if (cmd->sense_reason) {
                        if (cmd->sense_reason == TCM_RESERVATION_CONFLICT) {
                                cmd->i_state = ISTATE_SEND_STATUS;
                                spin_unlock_bh(&cmd->istate_lock);
                                iscsit_add_cmd_to_response_queue(cmd, cmd->conn,
                                                cmd->i_state);
                                return 0;
                        }
                        spin_unlock_bh(&cmd->istate_lock);
                        if (cmd->se_cmd.transport_state & CMD_T_ABORTED)
                                return 0;
                        return transport_send_check_condition_and_sense(se_cmd,
                                        cmd->sense_reason, 0);
                }
                /*
                 * Special case for delayed CmdSN with Immediate
                 * Data and/or Unsolicited Data Out attached.
                 */
                if (cmd->immediate_data) {
                        if (cmd->cmd_flags & ICF_GOT_LAST_DATAOUT) {
                                spin_unlock_bh(&cmd->istate_lock);
                                target_execute_cmd(&cmd->se_cmd);
                                return 0;
                        }
                        spin_unlock_bh(&cmd->istate_lock);

                        if (!(cmd->cmd_flags &
                                        ICF_NON_IMMEDIATE_UNSOLICITED_DATA)) {
                                if (cmd->se_cmd.transport_state & CMD_T_ABORTED)
                                        return 0;

                                iscsit_set_dataout_sequence_values(cmd);
                                conn->conn_transport->iscsit_get_dataout(conn, cmd, false);
                        }
                        return 0;
                }
                /*
                 * The default handler.
                 */
                spin_unlock_bh(&cmd->istate_lock);

                if ((cmd->data_direction == DMA_TO_DEVICE) &&
                    !(cmd->cmd_flags & ICF_NON_IMMEDIATE_UNSOLICITED_DATA)) {
                        if (cmd->se_cmd.transport_state & CMD_T_ABORTED)
                                return 0;

                        iscsit_set_unsolicited_dataout(cmd);
                }
                return target_submit(&cmd->se_cmd);

        case ISCSI_OP_NOOP_OUT:
        case ISCSI_OP_TEXT:
                spin_unlock_bh(&cmd->istate_lock);
                iscsit_add_cmd_to_response_queue(cmd, cmd->conn, cmd->i_state);
                break;
        case ISCSI_OP_SCSI_TMFUNC:
                if (cmd->se_cmd.se_tmr_req->response) {
                        spin_unlock_bh(&cmd->istate_lock);
                        iscsit_add_cmd_to_response_queue(cmd, cmd->conn,
                                        cmd->i_state);
                        return 0;
                }
                spin_unlock_bh(&cmd->istate_lock);

                return transport_generic_handle_tmr(&cmd->se_cmd);
        case ISCSI_OP_LOGOUT:
                spin_unlock_bh(&cmd->istate_lock);
                switch (cmd->logout_reason) {
                case ISCSI_LOGOUT_REASON_CLOSE_SESSION:
                        lr = iscsit_logout_closesession(cmd, cmd->conn);
                        break;
                case ISCSI_LOGOUT_REASON_CLOSE_CONNECTION:
                        lr = iscsit_logout_closeconnection(cmd, cmd->conn);
                        break;
                case ISCSI_LOGOUT_REASON_RECOVERY:
                        lr = iscsit_logout_removeconnforrecovery(cmd, cmd->conn);
                        break;
                default:
                        pr_err("Unknown iSCSI Logout Request Code:"
                                " 0x%02x\n", cmd->logout_reason);
                        return -1;
                }

                return lr;
        default:
                spin_unlock_bh(&cmd->istate_lock);
                pr_err("Cannot perform out of order execution for"
                " unknown iSCSI Opcode: 0x%02x\n", cmd->iscsi_opcode);
                return -1;
        }

        return 0;
}

void iscsit_free_all_ooo_cmdsns(struct iscsit_session *sess)
{
        struct iscsi_ooo_cmdsn *ooo_cmdsn, *ooo_cmdsn_tmp;

        mutex_lock(&sess->cmdsn_mutex);
        list_for_each_entry_safe(ooo_cmdsn, ooo_cmdsn_tmp,
                        &sess->sess_ooo_cmdsn_list, ooo_list) {

                list_del(&ooo_cmdsn->ooo_list);
                kmem_cache_free(lio_ooo_cache, ooo_cmdsn);
        }
        mutex_unlock(&sess->cmdsn_mutex);
}

int iscsit_handle_ooo_cmdsn(
        struct iscsit_session *sess,
        struct iscsit_cmd *cmd,
        u32 cmdsn)
{
        int batch = 0;
        struct iscsi_ooo_cmdsn *ooo_cmdsn = NULL, *ooo_tail = NULL;

        cmd->deferred_i_state           = cmd->i_state;
        cmd->i_state                    = ISTATE_DEFERRED_CMD;
        cmd->cmd_flags                  |= ICF_OOO_CMDSN;

        if (list_empty(&sess->sess_ooo_cmdsn_list))
                batch = 1;
        else {
                ooo_tail = list_entry(sess->sess_ooo_cmdsn_list.prev,
                                typeof(*ooo_tail), ooo_list);
                if (ooo_tail->cmdsn != (cmdsn - 1))
                        batch = 1;
        }

        ooo_cmdsn = iscsit_allocate_ooo_cmdsn();
        if (!ooo_cmdsn)
                return -ENOMEM;

        ooo_cmdsn->cmd                  = cmd;
        ooo_cmdsn->batch_count          = (batch) ?
                                          (cmdsn - sess->exp_cmd_sn) : 1;
        ooo_cmdsn->cid                  = cmd->conn->cid;
        ooo_cmdsn->exp_cmdsn            = sess->exp_cmd_sn;
        ooo_cmdsn->cmdsn                = cmdsn;

        if (iscsit_attach_ooo_cmdsn(sess, ooo_cmdsn) < 0) {
                kmem_cache_free(lio_ooo_cache, ooo_cmdsn);
                return -ENOMEM;
        }

        return 0;
}

static int iscsit_set_dataout_timeout_values(
        struct iscsit_cmd *cmd,
        u32 *offset,
        u32 *length)
{
        struct iscsit_conn *conn = cmd->conn;
        struct iscsi_r2t *r2t;

        if (cmd->unsolicited_data) {
                *offset = 0;
                *length = (conn->sess->sess_ops->FirstBurstLength >
                           cmd->se_cmd.data_length) ?
                           cmd->se_cmd.data_length :
                           conn->sess->sess_ops->FirstBurstLength;
                return 0;
        }

        spin_lock_bh(&cmd->r2t_lock);
        if (list_empty(&cmd->cmd_r2t_list)) {
                pr_err("cmd->cmd_r2t_list is empty!\n");
                spin_unlock_bh(&cmd->r2t_lock);
                return -1;
        }

        list_for_each_entry(r2t, &cmd->cmd_r2t_list, r2t_list) {
                if (r2t->sent_r2t && !r2t->recovery_r2t && !r2t->seq_complete) {
                        *offset = r2t->offset;
                        *length = r2t->xfer_len;
                        spin_unlock_bh(&cmd->r2t_lock);
                        return 0;
                }
        }
        spin_unlock_bh(&cmd->r2t_lock);

        pr_err("Unable to locate any incomplete DataOUT"
                " sequences for ITT: 0x%08x.\n", cmd->init_task_tag);

        return -1;
}

/*
 *      NOTE: Called from interrupt (timer) context.
 */
void iscsit_handle_dataout_timeout(struct timer_list *t)
{
        u32 pdu_length = 0, pdu_offset = 0;
        u32 r2t_length = 0, r2t_offset = 0;
        struct iscsit_cmd *cmd = timer_container_of(cmd, t, dataout_timer);
        struct iscsit_conn *conn = cmd->conn;
        struct iscsit_session *sess = NULL;
        struct iscsi_node_attrib *na;

        iscsit_inc_conn_usage_count(conn);

        spin_lock_bh(&cmd->dataout_timeout_lock);
        if (cmd->dataout_timer_flags & ISCSI_TF_STOP) {
                spin_unlock_bh(&cmd->dataout_timeout_lock);
                iscsit_dec_conn_usage_count(conn);
                return;
        }
        cmd->dataout_timer_flags &= ~ISCSI_TF_RUNNING;
        sess = conn->sess;
        na = iscsit_tpg_get_node_attrib(sess);

        if (!sess->sess_ops->ErrorRecoveryLevel) {
                pr_err("Unable to recover from DataOut timeout while"
                        " in ERL=0, closing iSCSI connection for I_T Nexus"
                        " %s,i,0x%6phN,%s,t,0x%02x\n",
                        sess->sess_ops->InitiatorName, sess->isid,
                        sess->tpg->tpg_tiqn->tiqn, (u32)sess->tpg->tpgt);
                goto failure;
        }

        if (++cmd->dataout_timeout_retries == na->dataout_timeout_retries) {
                pr_err("Command ITT: 0x%08x exceeded max retries"
                        " for DataOUT timeout %u, closing iSCSI connection for"
                        " I_T Nexus %s,i,0x%6phN,%s,t,0x%02x\n",
                        cmd->init_task_tag, na->dataout_timeout_retries,
                        sess->sess_ops->InitiatorName, sess->isid,
                        sess->tpg->tpg_tiqn->tiqn, (u32)sess->tpg->tpgt);
                goto failure;
        }

        cmd->cmd_flags |= ICF_WITHIN_COMMAND_RECOVERY;

        if (conn->sess->sess_ops->DataSequenceInOrder) {
                if (conn->sess->sess_ops->DataPDUInOrder) {
                        pdu_offset = cmd->write_data_done;
                        if ((pdu_offset + (conn->sess->sess_ops->MaxBurstLength -
                             cmd->next_burst_len)) > cmd->se_cmd.data_length)
                                pdu_length = (cmd->se_cmd.data_length -
                                        cmd->write_data_done);
                        else
                                pdu_length = (conn->sess->sess_ops->MaxBurstLength -
                                                cmd->next_burst_len);
                } else {
                        pdu_offset = cmd->seq_start_offset;
                        pdu_length = (cmd->seq_end_offset -
                                cmd->seq_start_offset);
                }
        } else {
                if (iscsit_set_dataout_timeout_values(cmd, &pdu_offset,
                                &pdu_length) < 0)
                        goto failure;
        }

        if (iscsit_recalculate_dataout_values(cmd, pdu_offset, pdu_length,
                        &r2t_offset, &r2t_length) < 0)
                goto failure;

        pr_debug("Command ITT: 0x%08x timed out waiting for"
                " completion of %sDataOUT Sequence Offset: %u, Length: %u\n",
                cmd->init_task_tag, (cmd->unsolicited_data) ? "Unsolicited " :
                "", r2t_offset, r2t_length);

        if (iscsit_send_recovery_r2t(cmd, r2t_offset, r2t_length) < 0)
                goto failure;

        iscsit_start_dataout_timer(cmd, conn);
        spin_unlock_bh(&cmd->dataout_timeout_lock);
        iscsit_dec_conn_usage_count(conn);

        return;

failure:
        spin_unlock_bh(&cmd->dataout_timeout_lock);
        iscsit_fill_cxn_timeout_err_stats(sess);
        iscsit_cause_connection_reinstatement(conn, 0);
        iscsit_dec_conn_usage_count(conn);
}

void iscsit_mod_dataout_timer(struct iscsit_cmd *cmd)
{
        struct iscsit_conn *conn = cmd->conn;
        struct iscsit_session *sess = conn->sess;
        struct iscsi_node_attrib *na = iscsit_tpg_get_node_attrib(sess);

        spin_lock_bh(&cmd->dataout_timeout_lock);
        if (!(cmd->dataout_timer_flags & ISCSI_TF_RUNNING)) {
                spin_unlock_bh(&cmd->dataout_timeout_lock);
                return;
        }

        mod_timer(&cmd->dataout_timer,
                (get_jiffies_64() + na->dataout_timeout * HZ));
        pr_debug("Updated DataOUT timer for ITT: 0x%08x",
                        cmd->init_task_tag);
        spin_unlock_bh(&cmd->dataout_timeout_lock);
}

void iscsit_start_dataout_timer(
        struct iscsit_cmd *cmd,
        struct iscsit_conn *conn)
{
        struct iscsit_session *sess = conn->sess;
        struct iscsi_node_attrib *na = iscsit_tpg_get_node_attrib(sess);

        lockdep_assert_held(&cmd->dataout_timeout_lock);

        if (cmd->dataout_timer_flags & ISCSI_TF_RUNNING)
                return;

        pr_debug("Starting DataOUT timer for ITT: 0x%08x on"
                " CID: %hu.\n", cmd->init_task_tag, conn->cid);

        cmd->dataout_timer_flags &= ~ISCSI_TF_STOP;
        cmd->dataout_timer_flags |= ISCSI_TF_RUNNING;
        mod_timer(&cmd->dataout_timer, jiffies + na->dataout_timeout * HZ);
}

void iscsit_stop_dataout_timer(struct iscsit_cmd *cmd)
{
        spin_lock_bh(&cmd->dataout_timeout_lock);
        if (!(cmd->dataout_timer_flags & ISCSI_TF_RUNNING)) {
                spin_unlock_bh(&cmd->dataout_timeout_lock);
                return;
        }
        cmd->dataout_timer_flags |= ISCSI_TF_STOP;
        spin_unlock_bh(&cmd->dataout_timeout_lock);

        timer_delete_sync(&cmd->dataout_timer);

        spin_lock_bh(&cmd->dataout_timeout_lock);
        cmd->dataout_timer_flags &= ~ISCSI_TF_RUNNING;
        pr_debug("Stopped DataOUT Timer for ITT: 0x%08x\n",
                        cmd->init_task_tag);
        spin_unlock_bh(&cmd->dataout_timeout_lock);
}
EXPORT_SYMBOL(iscsit_stop_dataout_timer);