in iscsi/iscsi_target_tmr.c [545:772]
static int iscsit_task_reassign_prepare_write(
struct iscsi_tmr_req *tmr_req,
struct iscsi_conn *conn)
{
struct iscsi_cmd *cmd = tmr_req->ref_cmd;
struct iscsi_pdu *pdu = NULL;
struct iscsi_r2t *r2t = NULL, *r2t_tmp;
int first_incomplete_r2t = 1, i = 0;
/*
* The command was in the process of receiving Unsolicited DataOUT when
* the connection failed.
*/
if (cmd->unsolicited_data)
iscsit_task_reassign_prepare_unsolicited_dataout(cmd, conn);
/*
* The Initiator is requesting R2Ts starting from zero, skip
* checking acknowledged R2Ts and start checking struct iscsi_r2ts
* greater than zero.
*/
if (!tmr_req->exp_data_sn)
goto drop_unacknowledged_r2ts;
/*
* We now check that the PDUs in DataOUT sequences below
* the TMR TASK_REASSIGN ExpDataSN (R2TSN the Initiator is
* expecting next) have all the DataOUT they require to complete
* the DataOUT sequence. First scan from R2TSN 0 to TMR
* TASK_REASSIGN ExpDataSN-1.
*
* If we have not received all DataOUT in question, we must
* make sure to make the appropriate changes to values in
* struct iscsi_cmd (and elsewhere depending on session parameters)
* so iscsit_build_r2ts_for_cmd() in iscsit_task_reassign_complete_write()
* will resend a new R2T for the DataOUT sequences in question.
*/
spin_lock_bh(&cmd->r2t_lock);
if (list_empty(&cmd->cmd_r2t_list)) {
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
list_for_each_entry(r2t, &cmd->cmd_r2t_list, r2t_list) {
if (r2t->r2t_sn >= tmr_req->exp_data_sn)
continue;
/*
* Safely ignore Recovery R2Ts and R2Ts that have completed
* DataOUT sequences.
*/
if (r2t->seq_complete)
continue;
if (r2t->recovery_r2t)
continue;
/*
* DataSequenceInOrder=Yes:
*
* Taking into account the iSCSI implementation requirement of
* MaxOutstandingR2T=1 while ErrorRecoveryLevel>0 and
* DataSequenceInOrder=Yes, we must take into consideration
* the following:
*
* DataSequenceInOrder=No:
*
* Taking into account that the Initiator controls the (possibly
* random) PDU Order in (possibly random) Sequence Order of
* DataOUT the target requests with R2Ts, we must take into
* consideration the following:
*
* DataPDUInOrder=Yes for DataSequenceInOrder=[Yes,No]:
*
* While processing non-complete R2T DataOUT sequence requests
* the Target will re-request only the total sequence length
* minus current received offset. This is because we must
* assume the initiator will continue sending DataOUT from the
* last PDU before the connection failed.
*
* DataPDUInOrder=No for DataSequenceInOrder=[Yes,No]:
*
* While processing non-complete R2T DataOUT sequence requests
* the Target will re-request the entire DataOUT sequence if
* any single PDU is missing from the sequence. This is because
* we have no logical method to determine the next PDU offset,
* and we must assume the Initiator will be sending any random
* PDU offset in the current sequence after TASK_REASSIGN
* has completed.
*/
if (conn->sess->sess_ops->DataSequenceInOrder) {
if (!first_incomplete_r2t) {
cmd->r2t_offset -= r2t->xfer_len;
goto next;
}
if (conn->sess->sess_ops->DataPDUInOrder) {
cmd->data_sn = 0;
cmd->r2t_offset -= (r2t->xfer_len -
cmd->next_burst_len);
first_incomplete_r2t = 0;
goto next;
}
cmd->data_sn = 0;
cmd->r2t_offset -= r2t->xfer_len;
for (i = 0; i < cmd->pdu_count; i++) {
pdu = &cmd->pdu_list[i];
if (pdu->status != ISCSI_PDU_RECEIVED_OK)
continue;
if ((pdu->offset >= r2t->offset) &&
(pdu->offset < (r2t->offset +
r2t->xfer_len))) {
cmd->next_burst_len -= pdu->length;
cmd->write_data_done -= pdu->length;
pdu->status = ISCSI_PDU_NOT_RECEIVED;
}
}
first_incomplete_r2t = 0;
} else {
struct iscsi_seq *seq;
seq = iscsit_get_seq_holder(cmd, r2t->offset,
r2t->xfer_len);
if (!seq) {
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
cmd->write_data_done -=
(seq->offset - seq->orig_offset);
seq->data_sn = 0;
seq->offset = seq->orig_offset;
seq->next_burst_len = 0;
seq->status = DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY;
cmd->seq_send_order--;
if (conn->sess->sess_ops->DataPDUInOrder)
goto next;
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;
}
}
next:
cmd->outstanding_r2ts--;
}
spin_unlock_bh(&cmd->r2t_lock);
/*
* We now drop all unacknowledged R2Ts, ie: ExpDataSN from TMR
* TASK_REASSIGN to the last R2T in the list.. We are also careful
* to check that the Initiator is not requesting R2Ts for DataOUT
* sequences it has already completed.
*
* Free each R2T in question and adjust values in struct iscsi_cmd
* accordingly so iscsit_build_r2ts_for_cmd() do the rest of
* the work after the TMR TASK_REASSIGN Response is sent.
*/
drop_unacknowledged_r2ts:
cmd->cmd_flags &= ~ICF_SENT_LAST_R2T;
cmd->r2t_sn = tmr_req->exp_data_sn;
spin_lock_bh(&cmd->r2t_lock);
list_for_each_entry_safe(r2t, r2t_tmp, &cmd->cmd_r2t_list, r2t_list) {
/*
* Skip up to the R2T Sequence number provided by the
* iSCSI TASK_REASSIGN TMR
*/
if (r2t->r2t_sn < tmr_req->exp_data_sn)
continue;
if (r2t->seq_complete) {
pr_err("Initiator is requesting R2Ts from"
" R2TSN: 0x%08x, but R2TSN: 0x%08x, Offset: %u,"
" Length: %u is already complete."
" BAD INITIATOR ERL=2 IMPLEMENTATION!\n",
tmr_req->exp_data_sn, r2t->r2t_sn,
r2t->offset, r2t->xfer_len);
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
if (r2t->recovery_r2t) {
iscsit_free_r2t(r2t, cmd);
continue;
}
/* DataSequenceInOrder=Yes:
*
* Taking into account the iSCSI implementation requirement of
* MaxOutstandingR2T=1 while ErrorRecoveryLevel>0 and
* DataSequenceInOrder=Yes, it's safe to subtract the R2Ts
* entire transfer length from the commands R2T offset marker.
*
* DataSequenceInOrder=No:
*
* We subtract the difference from struct iscsi_seq between the
* current offset and original offset from cmd->write_data_done
* for account for DataOUT PDUs already received. Then reset
* the current offset to the original and zero out the current
* burst length, to make sure we re-request the entire DataOUT
* sequence.
*/
if (conn->sess->sess_ops->DataSequenceInOrder)
cmd->r2t_offset -= r2t->xfer_len;
else
cmd->seq_send_order--;
cmd->outstanding_r2ts--;
iscsit_free_r2t(r2t, cmd);
}
spin_unlock_bh(&cmd->r2t_lock);
return 0;
}