windows-nt/Source/XPSP1/NT/drivers/storage/classpnp/retry.c
2020-09-26 16:20:57 +08:00

349 lines
12 KiB
C

/*++
Copyright (C) Microsoft Corporation, 1991 - 1999
Module Name:
retry.c
Abstract:
Packet retry routines for CLASSPNP
Environment:
kernel mode only
Notes:
Revision History:
--*/
#include "classp.h"
#include "debug.h"
/*
* InterpretTransferPacketError
*
* Interpret the SRB error into a meaningful IRP status.
* ClassInterpretSenseInfo also may modify the SRB for the retry.
*
* Return TRUE iff packet should be retried.
*/
BOOLEAN InterpretTransferPacketError(PTRANSFER_PACKET Pkt)
{
BOOLEAN shouldRetry = FALSE;
PCDB pCdb = (PCDB)Pkt->Srb.Cdb;
/*
* Interpret the error using the returned sense info first.
*/
Pkt->RetryIntervalSec = 0;
if (pCdb->MEDIA_REMOVAL.OperationCode == SCSIOP_MEDIUM_REMOVAL){
/*
* This is an Ejection Control SRB. Interpret its sense info specially.
*/
shouldRetry = ClassInterpretSenseInfo(
Pkt->Fdo,
&Pkt->Srb,
IRP_MJ_SCSI,
0,
MAXIMUM_RETRIES - Pkt->NumRetries,
&Pkt->Irp->IoStatus.Status,
&Pkt->RetryIntervalSec);
if (shouldRetry){
/*
* If the device is not ready, wait at least 2 seconds before retrying.
*/
PSENSE_DATA senseInfoBuffer = Pkt->Srb.SenseInfoBuffer;
ASSERT(senseInfoBuffer);
if (((Pkt->Irp->IoStatus.Status == STATUS_DEVICE_NOT_READY) &&
(senseInfoBuffer->AdditionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY)) ||
(SRB_STATUS(Pkt->Srb.SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT)){
Pkt->RetryIntervalSec = MAX(Pkt->RetryIntervalSec, 2);
}
}
}
else if ((pCdb->MODE_SENSE.OperationCode == SCSIOP_MODE_SENSE) ||
(pCdb->MODE_SENSE.OperationCode == SCSIOP_MODE_SENSE10)){
/*
* This is an Mode Sense SRB. Interpret its sense info specially.
*/
shouldRetry = ClassInterpretSenseInfo(
Pkt->Fdo,
&Pkt->Srb,
IRP_MJ_SCSI,
0,
MAXIMUM_RETRIES - Pkt->NumRetries,
&Pkt->Irp->IoStatus.Status,
&Pkt->RetryIntervalSec);
if (shouldRetry){
/*
* If the device is not ready, wait at least 2 seconds before retrying.
*/
PSENSE_DATA senseInfoBuffer = Pkt->Srb.SenseInfoBuffer;
ASSERT(senseInfoBuffer);
if (((Pkt->Irp->IoStatus.Status == STATUS_DEVICE_NOT_READY) &&
(senseInfoBuffer->AdditionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY)) ||
(SRB_STATUS(Pkt->Srb.SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT)){
Pkt->RetryIntervalSec = MAX(Pkt->RetryIntervalSec, 2);
}
}
/*
* Some special cases for mode sense.
*/
if (Pkt->Irp->IoStatus.Status == STATUS_VERIFY_REQUIRED){
shouldRetry = TRUE;
}
else if (SRB_STATUS(Pkt->Srb.SrbStatus) == SRB_STATUS_DATA_OVERRUN){
/*
* This is a HACK.
* Atapi returns SRB_STATUS_DATA_OVERRUN when it really means
* underrun (i.e. success, and the buffer is longer than needed).
* So treat this as a success.
*/
Pkt->Irp->IoStatus.Status = STATUS_SUCCESS;
InterlockedExchangeAdd((PLONG)&Pkt->OriginalIrp->IoStatus.Information, (LONG)Pkt->Srb.DataTransferLength);
shouldRetry = FALSE;
}
}
else if (pCdb->CDB10.OperationCode == SCSIOP_READ_CAPACITY){
/*
* This is a Drive Capacity SRB. Interpret its sense info specially.
*/
shouldRetry = ClassInterpretSenseInfo(
Pkt->Fdo,
&Pkt->Srb,
IRP_MJ_SCSI,
0,
MAXIMUM_RETRIES - Pkt->NumRetries,
&Pkt->Irp->IoStatus.Status,
&Pkt->RetryIntervalSec);
if (Pkt->Irp->IoStatus.Status == STATUS_VERIFY_REQUIRED){
shouldRetry = TRUE;
}
}
else if ((pCdb->CDB10.OperationCode == SCSIOP_READ) ||
(pCdb->CDB10.OperationCode == SCSIOP_WRITE)){
/*
* This is a Read/Write Data packet.
*/
PIO_STACK_LOCATION origCurrentSp = IoGetCurrentIrpStackLocation(Pkt->OriginalIrp);
shouldRetry = ClassInterpretSenseInfo(
Pkt->Fdo,
&Pkt->Srb,
origCurrentSp->MajorFunction,
0,
MAXIMUM_RETRIES - Pkt->NumRetries,
&Pkt->Irp->IoStatus.Status,
&Pkt->RetryIntervalSec);
/*
* Deal with some special cases.
*/
if (Pkt->Irp->IoStatus.Status == STATUS_INSUFFICIENT_RESOURCES){
/*
* We are in extreme low-memory stress.
* We will retry in smaller chunks.
*/
shouldRetry = TRUE;
}
else if (TEST_FLAG(origCurrentSp->Flags, SL_OVERRIDE_VERIFY_VOLUME) &&
(Pkt->Irp->IoStatus.Status == STATUS_VERIFY_REQUIRED)){
/*
* We are still verifying a (possibly) reloaded disk/cdrom.
* So retry the request.
*/
Pkt->Irp->IoStatus.Status = STATUS_IO_DEVICE_ERROR;
shouldRetry = TRUE;
}
}
else {
DBGERR(("Unhandled SRB Function %xh in error path for packet %p (did miniport change Srb.Cdb.OperationCode ?)", (ULONG)pCdb->CDB10.OperationCode, Pkt));
}
return shouldRetry;
}
/*
* RetryTransferPacket
*
* Retry sending a TRANSFER_PACKET.
*
* Return TRUE iff the packet is complete.
* (if so the status in pkt->irp is the final status).
*/
BOOLEAN RetryTransferPacket(PTRANSFER_PACKET Pkt)
{
BOOLEAN packetDone;
DBGTRACE(ClassDebugTrace, ("retrying failed transfer (pkt=%ph, op=%s)", Pkt, DBGGETSCSIOPSTR(&Pkt->Srb)));
ASSERT(Pkt->NumRetries > 0);
Pkt->NumRetries--;
/*
* Tone down performance on the retry.
* This increases the chance for success on the retry.
* We've seen instances of drives that fail consistently but then start working
* once this scale-down is applied.
*/
SET_FLAG(Pkt->Srb.SrbFlags, SRB_FLAGS_DISABLE_DISCONNECT);
SET_FLAG(Pkt->Srb.SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
CLEAR_FLAG(Pkt->Srb.SrbFlags, SRB_FLAGS_QUEUE_ACTION_ENABLE);
Pkt->Srb.QueueTag = SP_UNTAGGED;
if (Pkt->Irp->IoStatus.Status == STATUS_INSUFFICIENT_RESOURCES){
PCDB pCdb = (PCDB)Pkt->Srb.Cdb;
BOOLEAN isReadWrite = ((pCdb->CDB10.OperationCode == SCSIOP_READ) ||
(pCdb->CDB10.OperationCode == SCSIOP_WRITE));
if (Pkt->InLowMemRetry || !isReadWrite){
/*
* This should never happen.
* The memory manager guarantees that at least four pages will
* be available to allow forward progress in the port driver.
* So a one-page transfer should never fail with insufficient resources.
*/
ASSERT(isReadWrite && !Pkt->InLowMemRetry);
packetDone = TRUE;
}
else {
/*
* We are in low-memory stress.
* Start the low-memory retry state machine, which tries to
* resend the packet in little one-page chunks.
*/
InitLowMemRetry( Pkt,
Pkt->BufPtrCopy,
Pkt->BufLenCopy,
Pkt->TargetLocationCopy);
StepLowMemRetry(Pkt);
packetDone = FALSE;
}
}
else {
/*
* Retry the packet by simply resending it after a delay.
* Put the packet back in the pending queue and
* schedule a timer to retry the transfer.
*
* Do not call SetupReadWriteTransferPacket again because:
* (1) The minidriver may have set some bits
* in the SRB that it needs again and
* (2) doing so would reset numRetries.
*
* BECAUSE we do not call SetupReadWriteTransferPacket again,
* we have to reset a couple fields in the SRB that
* some miniports overwrite when they fail an SRB.
*/
Pkt->Srb.DataBuffer = Pkt->BufPtrCopy;
Pkt->Srb.DataTransferLength = Pkt->BufLenCopy;
if (Pkt->RetryIntervalSec == 0){
/*
* Always delay by at least a little when retrying.
* Some problems (e.g. CRC errors) are not recoverable without a slight delay.
*/
LARGE_INTEGER timerPeriod;
timerPeriod.HighPart = -1;
timerPeriod.LowPart = -(LONG)((ULONG)MINIMUM_RETRY_UNITS*KeQueryTimeIncrement());
KeInitializeTimer(&Pkt->RetryTimer);
KeInitializeDpc(&Pkt->RetryTimerDPC, TransferPacketRetryTimerDpc, Pkt);
KeSetTimer(&Pkt->RetryTimer, timerPeriod, &Pkt->RetryTimerDPC);
}
else {
LARGE_INTEGER timerPeriod;
ASSERT(Pkt->RetryIntervalSec < 100); // sanity check
timerPeriod.HighPart = -1;
timerPeriod.LowPart = Pkt->RetryIntervalSec*-10000000;
KeInitializeTimer(&Pkt->RetryTimer);
KeInitializeDpc(&Pkt->RetryTimerDPC, TransferPacketRetryTimerDpc, Pkt);
KeSetTimer(&Pkt->RetryTimer, timerPeriod, &Pkt->RetryTimerDPC);
}
packetDone = FALSE;
}
return packetDone;
}
VOID TransferPacketRetryTimerDpc( IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
{
PTRANSFER_PACKET pkt = (PTRANSFER_PACKET)DeferredContext;
SubmitTransferPacket(pkt);
}
VOID InitLowMemRetry(PTRANSFER_PACKET Pkt, PVOID BufPtr, ULONG Len, LARGE_INTEGER TargetLocation)
{
ASSERT(Len > 0);
ASSERT(!Pkt->InLowMemRetry);
Pkt->InLowMemRetry = TRUE;
Pkt->LowMemRetry_remainingBufPtr = BufPtr;
Pkt->LowMemRetry_remainingBufLen = Len;
Pkt->LowMemRetry_nextChunkTargetLocation = TargetLocation;
}
/*
* StepLowMemRetry
*
* During extreme low-memory stress, this function retries
* a packet in small one-page chunks, sent serially.
*
* Returns TRUE iff the packet is done.
*/
BOOLEAN StepLowMemRetry(PTRANSFER_PACKET Pkt)
{
BOOLEAN packetDone;
if (Pkt->LowMemRetry_remainingBufLen == 0){
packetDone = TRUE;
}
else {
ULONG thisChunkLen;
ULONG bytesToNextPageBoundary;
/*
* Make sure the little chunk we send is <= a page length
* AND that it does not cross any page boundaries.
*/
bytesToNextPageBoundary = PAGE_SIZE-(ULONG)((ULONG_PTR)Pkt->LowMemRetry_remainingBufPtr%PAGE_SIZE);
thisChunkLen = MIN(Pkt->LowMemRetry_remainingBufLen, bytesToNextPageBoundary);
/*
* Set up the transfer packet for the new little chunk.
* This will reset numRetries so that we retry each chunk as required.
*/
SetupReadWriteTransferPacket(Pkt,
Pkt->LowMemRetry_remainingBufPtr,
thisChunkLen,
Pkt->LowMemRetry_nextChunkTargetLocation,
Pkt->OriginalIrp);
Pkt->LowMemRetry_remainingBufPtr += thisChunkLen;
Pkt->LowMemRetry_remainingBufLen -= thisChunkLen;
Pkt->LowMemRetry_nextChunkTargetLocation.QuadPart += thisChunkLen;
SubmitTransferPacket(Pkt);
packetDone = FALSE;
}
return packetDone;
}