// 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> *
******************************************************************************/
/* * 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));
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.
*/ staticint 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);
/* * 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);
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;
/* * 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);
/* * 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;
/* * 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);
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);
/* * 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);
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 = 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);
/* * 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++;
} elseif (pdu_count) break;
}
} else { struct iscsi_seq *seq = cmd->seq_ptr;
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;
/* * 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);
}
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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.