/*++ 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" /* * 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 NTAPI 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 NTAPI 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 NTAPI TransferPacketRetryTimerDpc(IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2) { PTRANSFER_PACKET pkt = (PTRANSFER_PACKET)DeferredContext; SubmitTransferPacket(pkt); } VOID NTAPI 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 NTAPI 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; }