/*++ Copyright (c) 1996-2000 Microsoft Corporation Module Name: mrb.cpp Abstract: This source file contains the state machine for MU requests blocks to the device. The bulk-only protocol specifices three stages for every transfer. The only entry point to the MRB state machine is MU_fStartMrb Environment: kernel mode Revision History: 06-12-2000 : started rewrite : georgioc 10-20-2000 : major cleanup (separated out the MRB state machine, from the partial write state machine, which is now in disk.cpp : mitchd 03-07-2001 : generalized the way write commands are handled to aid the partial write state machine (in disk.cpp) support more flexible block sizes : mitchd --*/ /*++ Description of MRB state machine changes started on 03/07/01. Even after the cleanup on 10-20-2000, there was still a special case in the state machine to transfer the data stage of an MRB from two buffers: the user's buffer, and the MU WriteBuffer used to maintain media block integrity. This code peered into disk.cpp's flags to figure out when this was necessary, somewhat of a hack. The problem with this code is that it only supports a media block size that is double the emulated sector size. We now need to lift this restriction in order to support larger media. So this change formalizes the hack by adding supporting a new MRB flag, MRB_FLAGS_SPLIT_WRITE, and to new fields filled out by disk.cpp: UserStartOffset, and UserEndOffset. When MRB_FLAGS_SPLIT_WRITE the transfer length, must be exactly one media block. The write is performed as follows: 1) Write UserStartOffset bytes from the beginning of the WriteBuffer. 2) Write (UserEndOffset-UserStartOffset) bytes from the beginning of the user buffer. 3) Write (MediaBlockSize-UserEndOffset) bytes from the WriteBuffer begining UserStartOffset bytes into the WriteBuffer. This process allows a write to be submitted from the WriteBuffer inserting an arbitrary number of bytes from the user buffer at any point. In the process the MRB state machine becomes more independent of the disk.cpp layer. The new code should be ever scalable to larger and larger media block sizes. --*/ //***************************************************************************** // I N C L U D E S //***************************************************************************** #include "mu.h" //***************************************************************************** // Local Functions //***************************************************************************** DEFINE_USB_DEBUG_FUNCTIONS("MU"); VOID FASTCALL MU_fCbwTransfer( IN PMU_DEVICE_EXTENSION DeviceExtension ); VOID MU_CbwCompletion ( IN PURB Urb, IN PVOID Context ); VOID FASTCALL MU_fDataTransfer ( PURB Urb, PMU_DEVICE_EXTENSION DeviceExtension ); VOID MU_DataCompletion ( IN PURB Urb, IN PVOID Context ); VOID FASTCALL MU_fCswTransfer ( IN PMU_DEVICE_EXTENSION DeviceExtension ); VOID MU_CswCompletion ( IN PURB Urb, IN PVOID Context ); // // MU_MrbTimeout is local, but declared also in mu.h, because // mu.cpp sets up the DPC at init time. // VOID MU_MrbTimeout ( IN PKDPC Dpc, IN PVOID Context, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ); VOID FASTCALL MU_fMrbErrorHandler( IN PMU_DEVICE_EXTENSION DeviceExtension, IN NTSTATUS Status ); VOID MU_ResetDeviceCompletionRoutine ( IN PURB Urb, IN PVOID Context ); #if DBG VOID FASTCALL MU_fValidateMrb( IN PMU_DEVICE_EXTENSION DeviceExtension ); #define MU_DBG_VALIDATE_MRB(DeviceExtension) MU_fValidateMrb(DeviceExtension) #else #define MU_DBG_VALIDATE_MRB(DeviceExtension) #endif //***************************************************************************** // Implementation //***************************************************************************** VOID FASTCALL MU_fStartMrb( IN PMU_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Description: Entry point for the MRB state machine. Does some sanity checks in debug, makes sure the device is still connected, and calls MU_fCbwTransfer - the first phase of any request. --*/ { KIRQL oldIrql; USB_DBG_ENTRY_PRINT(("MU_fStartMrb(0x%0.8x)", DeviceExtension)); // // In Debug perform some simple tests // to make sure that the MRB is well formed. // MU_DBG_VALIDATE_MRB(DeviceExtension); // // Raise Irql to synchronize with removal // oldIrql = KeRaiseIrqlToDpcLevel(); // // Make sure that the device is still there. // if(TEST_FLAG(DeviceExtension->DeviceFlags, DF_PENDING_REMOVE|DF_REMOVED)) { // // Call the callback with a failure. // USB_DBG_TRACE_PRINT(("Mrb.CompletionRoutine(0x%0.8x, STATUS_DEVICE_NOT_CONNECTED)", DeviceExtension)); DeviceExtension->Mrb.CompletionRoutine(DeviceExtension, STATUS_DEVICE_NOT_CONNECTED); } else { // // Send the CBW // ASSERT(!TEST_FLAG(DeviceExtension->DeviceFlags, DF_ANY_URB_PENDING|DF_RESET_STEPS)); MU_fCbwTransfer(DeviceExtension); } KeLowerIrql(oldIrql); return; } VOID FASTCALL MU_fCbwTransfer( IN PMU_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Description: Builds an URB for the CBW phase and send it. --*/ { PMU_REQUEST_BLOCK mrb; PCBW cbw; LARGE_INTEGER deltaTime; USB_DBG_ENTRY_PRINT(("MU_CbwTransfer(DeviceExtension=0x%0.8x)", DeviceExtension)); // // Get the mrb // mrb = &DeviceExtension->Mrb; // // Initialize the Command Block Wrapper // cbw = &DeviceExtension->Mrb.Cbw; cbw->dCBWSignature = CBW_SIGNATURE; cbw->dCBWTag = PtrToUlong(DeviceExtension->PendingIrp); cbw->dCBWDataTransferLength = mrb->TransferLength; cbw->bCBWFlags = (UCHAR)((mrb->Flags & MRB_FLAGS_DATA_IN) ? CBW_FLAGS_DATA_IN : CBW_FLAGS_DATA_OUT); cbw->bCBWLUN = 0; // Xbox supports only signle LUN cbw->bCDBLength = 10; //Xbox supports on 10 byte cdb's // // The caller filled out the CDB in the CBW // // // start the request timer // SET_FLAG(DeviceExtension->DeviceFlags, DF_MRB_TIMER_RUNNING); deltaTime.QuadPart = MRB_STANDARD_TIMEOUT * MRB_TIMEOUT_UNIT; KeSetTimer(&DeviceExtension->Mrb.Timer, deltaTime, &DeviceExtension->Mrb.TimeoutDpcObject); // // Build and submit an URB for the CBW // SET_FLAG(DeviceExtension->DeviceFlags, DF_PRIMARY_URB_PENDING); USB_BUILD_BULK_OR_INTERRUPT_TRANSFER( &DeviceExtension->Urb.BulkOrInterruptTransfer, DeviceExtension->BulkOutEndpointHandle, cbw, sizeof(CBW), USB_TRANSFER_DIRECTION_OUT, MU_CbwCompletion, DeviceExtension, FALSE //short transfer is an error ); // // Don't worry about errors the completion // routine will handle them. // ASSERT(DeviceExtension->MuInstance); DeviceExtension->MuInstance->Device->SubmitRequest(&DeviceExtension->Urb); USB_DBG_EXIT_PRINT(("MU_CbwTransfer returning")); return; } VOID MU_CbwCompletion ( IN PURB Urb, IN PVOID Context ) /* Routine Description: Completion routine for MU_fStartMrb. If any type of error occured (including a timeout) call MU_HandleBulkError() to handle it, and exit. On success, if a data phase is required start it, otherwise call MU_fCswTransfer. */ { PMU_DEVICE_EXTENSION deviceExtension; PMU_REQUEST_BLOCK mrb; USB_DBG_ENTRY_PRINT(("MU_CbwCompletion(Urb=0x%0.8x,Context=0x%0.8x)", Urb, Context)); deviceExtension = (PMU_DEVICE_EXTENSION) Context; ASSERT(Urb == &deviceExtension->Urb); // // If the URB failed (which includes being cancelled by the timer DPC) // call the error handling code. // if(USBD_ERROR(Urb->Header.Status) || TEST_FLAG(deviceExtension->DeviceFlags, DF_REMOVED|DF_PENDING_REMOVE) ) { USB_DBG_WARN_PRINT(("CBW transfer failed, usbdStatus=0x%0.8x", Urb->Header.Status)); CLEAR_FLAG(deviceExtension->DeviceFlags, DF_PRIMARY_URB_PENDING); MU_fMrbErrorHandler(deviceExtension, IUsbDevice::NtStatusFromUsbdStatus(Urb->Header.Status)); return; } mrb = &deviceExtension->Mrb; // The CBW Bulk Transfer was successful. Start the next phase, either // the Data Bulk Transfer or CSW Bulk Transfer, and do not complete the // request yet. // if(mrb->DataBuffer) { ASSERT(mrb->TransferLength != 0); mrb->BytesSubmitted = 0; // // Start the first URB // ASSERT(TEST_FLAG(deviceExtension->DeviceFlags, DF_PRIMARY_URB_PENDING)); MU_fDataTransfer(&deviceExtension->Urb, deviceExtension); // // If there is more data, start up with the second URB // if(mrb->BytesSubmitted < mrb->TransferLength) { SET_FLAG(deviceExtension->DeviceFlags, DF_SECONDARY_URB_PENDING); MU_fDataTransfer((PURB)&deviceExtension->BulkUrbSecondary, deviceExtension); } } else { ASSERT(0 == mrb->TransferLength); MU_fCswTransfer(deviceExtension); } USB_DBG_EXIT_PRINT(("MU_CbwCompletion returning")); return; } VOID FASTCALL MU_fDataTransfer ( PURB Urb, PMU_DEVICE_EXTENSION DeviceExtension ) /* Routine Description: Submit URB for the data stage of the MRB stage machine. It uses the URB passed to it. Since USB guarantees that multiple requests on the same endpoint will complete in sequence we can just keep alternating them until the whole transfer buffer is sent. */ { PMU_REQUEST_BLOCK mrb; PVOID endpointHandle; PVOID transferBuffer; ULONG transferLength; UCHAR direction; USB_DBG_ENTRY_PRINT(("MU_DataTransfer(Urb=0x%0.8x,DeviceExtension=0x%0.8x)", Urb, DeviceExtension)); mrb = &DeviceExtension->Mrb; // // Bulk IN or Bulk OUT? // if ((mrb->Flags & MRB_FLAGS_UNSPECIFIED_DIRECTION) == MRB_FLAGS_DATA_IN) { endpointHandle = DeviceExtension->BulkInEndpointHandle; direction = USB_TRANSFER_DIRECTION_IN; } else { ASSERT((mrb->Flags & MRB_FLAGS_UNSPECIFIED_DIRECTION) == MRB_FLAGS_DATA_OUT); endpointHandle = DeviceExtension->BulkOutEndpointHandle; direction = USB_TRANSFER_DIRECTION_OUT; } //** //** Calculate the transfer buffer for this URB //** if(TEST_FLAG(mrb->Flags,MRB_FLAGS_SPLIT_WRITE)) // // MRB_FLAGS_SPLIT_WRITE has three stages, handle // the correct one. { ASSERT(USB_TRANSFER_DIRECTION_OUT == direction); if (mrb->BytesSubmitted < mrb->UserStartOffset) // // First stage - from beginning of WriteBuffer // { transferBuffer = MU_DriverExtension.WriteBuffer + mrb->BytesSubmitted; } else if(mrb->BytesSubmitted < mrb->UserEndOffset) // // Second stage - from User's buffer // { transferBuffer = mrb->DataBuffer + (mrb->BytesSubmitted - mrb->UserStartOffset); } else // // Third stage - from end of WriteBuffer // { transferBuffer = MU_DriverExtension.WriteBuffer + mrb->UserStartOffset + (mrb->BytesSubmitted - mrb->UserEndOffset); } } else // // The MRB_FLAGS_SPLIT_WRITE is NOT set, so just do the simple thing // from the user buffer. { transferBuffer = mrb->DataBuffer + mrb->BytesSubmitted; } // // Calculate the transfer length // transferLength = mrb->TransferLength - mrb->BytesSubmitted; if(transferLength > MRB_TRANSFER_SIZE) { transferLength = MRB_TRANSFER_SIZE; } mrb->BytesSubmitted += transferLength; // // Build and submit an URB for the Transfer // USB_BUILD_BULK_OR_INTERRUPT_TRANSFER( &Urb->BulkOrInterruptTransfer, endpointHandle, transferBuffer, transferLength, direction, MU_DataCompletion, DeviceExtension, FALSE //short transfer is an error ); // // Extend the timeout. // LARGE_INTEGER deltaTime; deltaTime.QuadPart = MRB_DATA_TIMEOUT * MRB_TIMEOUT_UNIT; KeSetTimer(&DeviceExtension->Mrb.Timer, deltaTime, &DeviceExtension->Mrb.TimeoutDpcObject); // // Don't worry about errors the completion // routine will handle them. // ASSERT(DeviceExtension->MuInstance); DeviceExtension->MuInstance->Device->SubmitRequest(Urb); return; } VOID MU_DataCompletion ( IN PURB Urb, IN PVOID Context ) /*++ Routine Description: This is the completion routine for MU_fDataTransfer. On an error (from USB), it calls MU_HandleBulkErrors. Then it returns. If there is more data to submit it loops back to MU_fDataTransfer to keep the data moving. If there is no more data to submit, it checks to see if there is an other URB outstanding. If there are no more URBs outstanding, then it moves on to the next stage by calling MU_fTransferCsw. --*/ { PMU_DEVICE_EXTENSION deviceExtension = (PMU_DEVICE_EXTENSION) Context; PMU_REQUEST_BLOCK mrb; mrb = &deviceExtension->Mrb; // // Check to see if there is an error pending (from the other URB) // if(TEST_FLAG(deviceExtension->DeviceFlags,DF_ERROR_PENDING)) { USBD_STATUS usbdStatus; // // DF_ERROR_PENDING was set only so that we get here, // clear it now. // CLEAR_FLAG(deviceExtension->DeviceFlags,DF_ERROR_PENDING); // Grab the status from the OTHER Urb if(Urb == &deviceExtension->Urb) { usbdStatus = deviceExtension->BulkUrbSecondary.Hdr.Status; } else { usbdStatus = deviceExtension->Urb.Header.Status; } // // There is definately no URB pending now. // (it is more efficient to clear both flags, than to // figure out which one we need to clear). // CLEAR_FLAG(deviceExtension->DeviceFlags, DF_PRIMARY_URB_PENDING|DF_SECONDARY_URB_PENDING); MU_fMrbErrorHandler(deviceExtension, IUsbDevice::NtStatusFromUsbdStatus(usbdStatus)); return; } // // If this URB failed, cancel the other URB, if it is outstanding, // otherwise, start handling the error here. // if(USBD_ERROR(Urb->Header.Status) || TEST_FLAG(deviceExtension->DeviceFlags, DF_REMOVED|DF_PENDING_REMOVE) ) { USB_DBG_WARN_PRINT(("Data transfer failed (USB status)0x%0.8x", Urb->Header.Status)); // // On error, we figure out which URB we are processing and // clear its pending flag. If the URB is pending we need // to cancel it, and return. // if(Urb == &deviceExtension->Urb) { CLEAR_FLAG(deviceExtension->DeviceFlags,DF_PRIMARY_URB_PENDING); if(TEST_FLAG(deviceExtension->DeviceFlags,DF_SECONDARY_URB_PENDING)) { ASSERT(deviceExtension->MuInstance); SET_FLAG(deviceExtension->DeviceFlags,DF_ERROR_PENDING); deviceExtension->MuInstance->Device->CancelRequest((PURB)&deviceExtension->BulkUrbSecondary); return; } } else { ASSERT(Urb == (PURB)&deviceExtension->BulkUrbSecondary); CLEAR_FLAG(deviceExtension->DeviceFlags,DF_SECONDARY_URB_PENDING); if(TEST_FLAG(deviceExtension->DeviceFlags,DF_PRIMARY_URB_PENDING)) { ASSERT(deviceExtension->MuInstance); SET_FLAG(deviceExtension->DeviceFlags,DF_ERROR_PENDING); deviceExtension->MuInstance->Device->CancelRequest(&deviceExtension->Urb); return; } } // // If we are here there are no outstanding URBs for this device, so // we can start the error handling. // MU_fMrbErrorHandler(deviceExtension, IUsbDevice::NtStatusFromUsbdStatus(Urb->Header.Status)); return; } // // Ugh, that was a lot of error handling code, // but we want to be robust! // // // If there is more data to submit // then call MU_fDataTransfer, // and return. // if(mrb->BytesSubmitted < mrb->TransferLength) { MU_fDataTransfer(Urb, deviceExtension); return; } // // We could just move on to the CSW stage now, // even if the other URB is still pending for // a read or write. This is acceptable, because // of 3.3 of the Bulk-Only specification. However, // we do not lose measurable efficiency by only // continuing if there are no more data URB // outstanding. This method reduces the number // of possible error cases we need to check, // and there are already too many! // if(Urb == &deviceExtension->Urb) { CLEAR_FLAG(deviceExtension->DeviceFlags,DF_PRIMARY_URB_PENDING); if(TEST_FLAG(deviceExtension->DeviceFlags,DF_SECONDARY_URB_PENDING)) { // Nothing to do, because our twin URB is still pending return; } } else { ASSERT(Urb == (PURB)&deviceExtension->BulkUrbSecondary); CLEAR_FLAG(deviceExtension->DeviceFlags,DF_SECONDARY_URB_PENDING); if(TEST_FLAG(deviceExtension->DeviceFlags,DF_PRIMARY_URB_PENDING)) { // Nothing to do, because our twin URB is still pending return; } } // // Reset the primary URB flag as MU_fCswTransfer will use it. // SET_FLAG(deviceExtension->DeviceFlags,DF_PRIMARY_URB_PENDING); // // The data transfer is over, go on to CSW stage // MU_fCswTransfer(deviceExtension); return; } VOID FASTCALL MU_fCswTransfer ( IN PMU_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Description: Builds and submits an URB for the CswTransfer. It is broken out as a separate routineas it is called from two places (MU_DataCompletion and MU_CbwCompletion). --*/ { USB_DBG_ENTRY_PRINT(("MU_DataTransfer(DeviceExtension=0x%0.8x)",DeviceExtension)); USB_BUILD_BULK_OR_INTERRUPT_TRANSFER( &DeviceExtension->Urb.BulkOrInterruptTransfer, DeviceExtension->BulkInEndpointHandle, &DeviceExtension->Mrb.Csw, sizeof(CSW), USB_TRANSFER_DIRECTION_IN, MU_CswCompletion, DeviceExtension, FALSE //short transfer is an error ); // // Extend the timeout for the CSW. // LARGE_INTEGER deltaTime; deltaTime.QuadPart = ((LONGLONG)DeviceExtension->Mrb.TimeOutValue) * MRB_TIMEOUT_UNIT; KeSetTimer(&DeviceExtension->Mrb.Timer, deltaTime, &DeviceExtension->Mrb.TimeoutDpcObject); // // Submit the URB, the completion routine will worry about errors. // ASSERT(DeviceExtension->MuInstance); DeviceExtension->MuInstance->Device->SubmitRequest(&DeviceExtension->Urb); USB_DBG_EXIT_PRINT(("MU_CswTransfer returning")); return; } VOID MU_CswCompletion ( IN PURB Urb, IN PVOID Context ) /*++ Routine Description: Completion routine for the CSW transfer. Checks for a number of possible error conditions. If any are found it calls MU_HandleBulkError. Otherwise calls the MRBs completion routine with STATUS_SUCCESS. --*/ { PMU_DEVICE_EXTENSION deviceExtension = (PMU_DEVICE_EXTENSION)Context; NTSTATUS status = STATUS_SUCCESS; USB_DBG_ENTRY_PRINT(("MU_CswCompletion(Urb=0x%0.8x,Context=0x%0.8x)", Urb, Context)); // // Cancel the timer // if(TEST_FLAG(deviceExtension->DeviceFlags, DF_MRB_TIMER_RUNNING)) { KeCancelTimer(&deviceExtension->Mrb.Timer); CLEAR_FLAG(deviceExtension->DeviceFlags, DF_MRB_TIMER_RUNNING); } CLEAR_FLAG(deviceExtension->DeviceFlags,DF_PRIMARY_URB_PENDING); if(USBD_ERROR(Urb->Header.Status)) { // The Data Bulk Transfer was not successful. Look at how the // the transfer failed to figure out how to recover. // USB_DBG_WARN_PRINT(("CSW transfer failed, usbdStatus=0x%0.8x", Urb->Header.Status)); status = IUsbDevice::NtStatusFromUsbdStatus(Urb->Header.Status); } // // Check Signature. // if(deviceExtension->Mrb.Csw.dCSWSignature != CSW_SIGNATURE) { USB_DBG_WARN_PRINT(("CSW signature incorrect.")); status = STATUS_UNSUCCESSFUL; } // // Check Tag // if(deviceExtension->Mrb.Csw.dCSWTag != deviceExtension->Mrb.Cbw.dCBWTag) { USB_DBG_WARN_PRINT(("CSW tag incorrect, expecting 0x%0.8x, was 0x%0.8x.", deviceExtension->Mrb.Cbw.dCBWTag, deviceExtension->Mrb.Csw.dCSWTag )); status = STATUS_UNSUCCESSFUL; } // // Check For Phase Error (means missed handshake in the protocol) // if(deviceExtension->Mrb.Csw.bCSWStatus == CSW_STATUS_PHASE_ERROR) { USB_DBG_WARN_PRINT(("CSW returned CSW_STATUS_PHASE_ERROR\n")); status = STATUS_UNSUCCESSFUL; } // // Check for failed CSW. Basically the device had trouble // completing the command. It could be that we sent an unsupported // command or that we tried to write past the end of the media // or some other error on our part. OR, THE MEDIA IS BAD. // On a read or write, we ASSUME that this driver did not screw up // and infact a media block has failed!!! It sucks that we don't // have more specific errors, but we didn't invent the protocol, // we just borrowed it from the USB mass storage committee. // if(deviceExtension->Mrb.Csw.bCSWStatus == CSW_STATUS_FAILED) { USB_DBG_WARN_PRINT(("CSW returned CSW_STATUS_FAILED\n")); status = STATUS_DATA_ERROR; } ASSERT(!TEST_FLAG(deviceExtension->DeviceFlags, DF_ANY_URB_PENDING)); // // If any error occurred, call the handle bulk error routine. // if(NT_ERROR(status)) { MU_fMrbErrorHandler(deviceExtension, status); return; } ASSERT(deviceExtension->Mrb.Csw.bCSWStatus == CSW_STATUS_GOOD); // // Call the MRB completion routine with a success code // USB_DBG_TRACE_PRINT(("Mrb.CompletionRoutine(0x%0.8x, STATUS_SUCCESS)", deviceExtension)); deviceExtension->Mrb.CompletionRoutine(deviceExtension, STATUS_SUCCESS); USB_DBG_EXIT_PRINT(("MU_CswCompletion returning")); return; } VOID MU_MrbTimeout ( IN PKDPC Dpc, IN PVOID Context, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) /*++ Routine Description: This is the DPC routine for the MRB timeout timer. The timer is set before the Cbw is transmitted, and canceled when the bulk-only request completes by receiving a Csw, or when error occurs aborting the request. The timer is intended to protect against devices that NAK forever. Device that are just extremely slow will hit this as well. This code does not protect against software bugs in this driver or lower in the USB stack. All this routine does is cancel any URBs that are outstanding. This will break the deadlock, and the normal error handling in the state machine will kick in. Timeout Timing: The timer is set at CBW, and extended at each data stage and at CSW. The length of the timeout for the data stages and CSW is fixed. The caller in the disk layer can set the timeout for the CBW. This is to accomodate commands like VERIFY that may require very long timeouts on the CBW and they don't have a data stage. --*/ { PMU_DEVICE_EXTENSION deviceExtension = (PMU_DEVICE_EXTENSION) Context; USB_DBG_WARN_PRINT(("MRB state machine timed out.")); CLEAR_FLAG(deviceExtension->DeviceFlags, DF_MRB_TIMER_RUNNING); if(TEST_FLAG(deviceExtension->DeviceFlags,DF_PRIMARY_URB_PENDING)) { ASSERT(deviceExtension->MuInstance); deviceExtension->MuInstance->Device->CancelRequest(&deviceExtension->Urb); } if(TEST_FLAG(deviceExtension->DeviceFlags, DF_SECONDARY_URB_PENDING)) { ASSERT(deviceExtension->MuInstance); deviceExtension->MuInstance->Device->CancelRequest((PURB)&deviceExtension->BulkUrbSecondary); } } VOID FASTCALL MU_fMrbErrorHandler( IN PMU_DEVICE_EXTENSION DeviceExtension, IN NTSTATUS Status ) /*++ Routine Description: This routine is called whenever a stage of the Mrb state machine detects an error (USB failure, device removed, CSW problem, or timeout). There should be no outstanding Urbs when this routine is called. If the MRB timer is running, cancel it. If the device has been removed, this routine cleans up the Mrb state machine and calls the Mrb competion routine with STATUS_DEVICE_NOT_CONNECTED. If the device is still present, it kicks of the USB bulk-only reset sequence. This a fairly gentle way to get the bulk-only transport protocol back in-sync between the device and this driver. See the MU_ResetDeviceCompletionRoutine description for details on the reset sequence. This routine performs the first two steps, then second of which is asynchronous and completes in MU_ResetDeviceCompletionRoutine. --*/ { PMU_INSTANCE muInstance = DeviceExtension->MuInstance; // // Stop the Mrb Timer, if it is running // if(TEST_FLAG(DeviceExtension->DeviceFlags, DF_MRB_TIMER_RUNNING)) { KeCancelTimer(&DeviceExtension->Mrb.Timer); CLEAR_FLAG(DeviceExtension->DeviceFlags, DF_MRB_TIMER_RUNNING); } // // There should be no URB's pending when this routine is hit. // ASSERT(!TEST_FLAG(DeviceExtension->DeviceFlags, DF_ANY_URB_PENDING)); // // Check if the device has been removed // if(TEST_FLAG(DeviceExtension->DeviceFlags, DF_REMOVED|DF_PENDING_REMOVE)) { USB_DBG_TRACE_PRINT(("Mrb.CompletionRoutine(0x%0.8x, STATUS_DEVICE_NOT_CONNECTED)", DeviceExtension)); DeviceExtension->Mrb.CompletionRoutine(DeviceExtension, STATUS_DEVICE_NOT_CONNECTED); return; } ASSERT(muInstance); //** //** Start the reset sequence, //** USB_DBG_TRACE_PRINT(("Initiating Bulk-Only reset sequence.")); SET_FLAG(DeviceExtension->DeviceFlags, DF_RESET_STEP1); // // stored the failed status, so that we still have it // after a reset. Note this field is a union with // BytesSubmitted so it is not valid if we resubmit the MRB. // DeviceExtension->Mrb.FailedStatus = Status; // // Clear the data toggle and software halt flag. This is a // synchronous USB request and should never fail. // USB_BUILD_SET_ENDPOINT_STATE( &DeviceExtension->Urb.GetSetEndpointState, DeviceExtension->BulkInEndpointHandle, USB_ENDPOINT_STATE_DATA_TOGGLE_RESET | USB_ENDPOINT_STATE_CLEAR_HALT ); if(USBD_ERROR(muInstance->Device->SubmitRequest(&DeviceExtension->Urb))) { USB_DBG_WARN_PRINT(("MU_ResetDeviceDpc: Bulk-IN SetEndpointStatus failed!\n")); ASSERT(FALSE); } // // Set the MRB timer to time the reset. // LARGE_INTEGER deltaTime; deltaTime.QuadPart = MRB_RESET_TIMEOUT*MRB_TIMEOUT_UNIT; SET_FLAG(DeviceExtension->DeviceFlags, DF_MRB_TIMER_RUNNING); SET_FLAG(DeviceExtension->DeviceFlags,DF_PRIMARY_URB_PENDING); KeSetTimer(&DeviceExtension->Mrb.Timer, deltaTime, &DeviceExtension->Mrb.TimeoutDpcObject); // // now send CLEAR_FEATURE(ENDPOINT_STALL) for the IN endpoint // USB_BUILD_CLEAR_FEATURE( &DeviceExtension->Urb.ControlTransfer, USB_COMMAND_TO_ENDPOINT, USB_FEATURE_ENDPOINT_STALL, muInstance->BulkInEndpointAddress, (PURB_COMPLETE_PROC)MU_ResetDeviceCompletionRoutine, DeviceExtension ); // // The completion routine deal with errors. // muInstance->Device->SubmitRequest(&DeviceExtension->Urb); } VOID MU_ResetDeviceCompletionRoutine ( IN PURB Urb, IN PVOID Context ) /*++ Routine Description: This routine is the heart of the USB Bulk-Only Mass Storage Reset Sequence. The reset sequence consists of: 1) Clear the STALL on the Bulk-IN endpoint. 2) Clear the STALL on the Bulk-OUT endpoint. 3) Send the the BULK_ONLY_MASS_STORAGE_RESET command on the control pipe. Clearing a STALL is a two step process on Xbox, first you send a SET_ENDPOINT_STATE Urb to clear the endpoint HALT bit, and to reset the data toggle. Then you send a CLEAR_FEATURE(ENDPOINT_STALL) over the control endpoint to the device, so it clears its STALL bit. Step 1) was initiated by MU_fMrbErrorHandler. So this routine verifies that that succeeded. Steps 2) and 3) are both asynchronous. So this routine is a multistage state machine. DeviceFlags are used to keep track of the stage. Any failure during the reset sequence, results in reporting the device as not responding to the core USB stack, if MU_Remove has not been called in the interm. The core USB stack will drop the device (notifying everyone it is gone), and then reenumerate. This procedure is not gentle! At the end of the reset sequence the retry count of the MRB is decremented and checked, if there are still retries remaining, then MU_fCbwTransfer is called to restart the Mrb. Otherwise, the MRB completion routine is called with STATUS_UNSUCCESSFUL. Unfortunately, the original error (timeout, or transmission failure) has not been propogated. --*/ { PMU_DEVICE_EXTENSION deviceExtension = (PMU_DEVICE_EXTENSION) Context; ULONG resetStage = deviceExtension->DeviceFlags & DF_RESET_STEPS; PMU_INSTANCE muInstance = deviceExtension->MuInstance; // // Mark that the primary URB is not outstanding // CLEAR_FLAG(deviceExtension->DeviceFlags,DF_PRIMARY_URB_PENDING); // // Stop the MRB timer if it is running // if(TEST_FLAG(deviceExtension->DeviceFlags, DF_MRB_TIMER_RUNNING)) { KeCancelTimer(&deviceExtension->Mrb.Timer); CLEAR_FLAG(deviceExtension->DeviceFlags, DF_MRB_TIMER_RUNNING); } // // Check for device not connected. // if(TEST_FLAG(deviceExtension->DeviceFlags, DF_REMOVED|DF_PENDING_REMOVE)) { USB_DBG_TRACE_PRINT(("Mrb.CompletionRoutine(0x%0.8x, STATUS_DEVICE_NOT_CONNECTED)", deviceExtension)); deviceExtension->Mrb.CompletionRoutine(deviceExtension, STATUS_DEVICE_NOT_CONNECTED); return; } ASSERT(muInstance); // // Check for USB errors // if(USBD_ERROR(Urb->Header.Status)) { USB_DBG_WARN_PRINT(("Reset Sequence Failed, reporting device as not responding.")); // // Clear all the reset flags, reset is over, wherever it was // CLEAR_FLAG(deviceExtension->DeviceFlags, DF_RESET_STEPS); // // Report the device as not responding // muInstance->Device->DeviceNotResponding(); // // Fail the MRB as STATUS_DEVICE_NOT_CONNECTED // USB_DBG_TRACE_PRINT(("Mrb.CompletionRoutine(0x%0.8x, STATUS_DEVICE_NOT_CONNECTED)", deviceExtension)); deviceExtension->Mrb.CompletionRoutine(deviceExtension, STATUS_DEVICE_NOT_CONNECTED); return; } // // Move on to the next stage of the Reset sequence. // (note that we are switching on the resetStage // of the just completed stage.) // switch(resetStage) { case DF_RESET_STEP1: // // Switch the flag to indicate step2 // CLEAR_FLAG(deviceExtension->DeviceFlags, DF_RESET_STEP1); SET_FLAG(deviceExtension->DeviceFlags, DF_RESET_STEP2); // // Clear the data toggle and software halt flag for Bulk-OUT // This is a synchronous USB request and should never fail. // USB_BUILD_SET_ENDPOINT_STATE( &deviceExtension->Urb.GetSetEndpointState, deviceExtension->BulkOutEndpointHandle, USB_ENDPOINT_STATE_DATA_TOGGLE_RESET | USB_ENDPOINT_STATE_CLEAR_HALT ); if(USBD_ERROR(muInstance->Device->SubmitRequest(&deviceExtension->Urb))) { USB_DBG_WARN_PRINT(("MU_ResetDeviceDpc: Bulk-OUT SetEndpointStatus failed!")); ASSERT(FALSE); } // // Build CLEAR_FEATURE(ENDPOINT_STALL) for the OUT endpoint // (it will get sent at the end of this routine.) USB_BUILD_CLEAR_FEATURE( &deviceExtension->Urb.ControlTransfer, USB_COMMAND_TO_ENDPOINT, USB_FEATURE_ENDPOINT_STALL, muInstance->BulkOutEndpointAddress, (PURB_COMPLETE_PROC)MU_ResetDeviceCompletionRoutine, deviceExtension ); break; case DF_RESET_STEP2: // // Switch the flag to indicate step2 // CLEAR_FLAG(deviceExtension->DeviceFlags, DF_RESET_STEP2); SET_FLAG(deviceExtension->DeviceFlags, DF_RESET_STEP3); // // Build the BULK_ONLY_MASS_STORAGE_RESET command // (it will get sent at the end of this routine.) USB_BUILD_CONTROL_TRANSFER( &deviceExtension->Urb.ControlTransfer, NULL, NULL, 0, USB_TRANSFER_DIRECTION_OUT, MU_ResetDeviceCompletionRoutine, deviceExtension, FALSE, (USB_HOST_TO_DEVICE | USB_CLASS_COMMAND | USB_COMMAND_TO_INTERFACE), BULK_ONLY_MASS_STORAGE_RESET, 0, muInstance->InterfaceNumber, 0); break; case DF_RESET_STEP3: // // Clear DF_RESET_STEP3, the reset sequence is over. // CLEAR_FLAG(deviceExtension->DeviceFlags, DF_RESET_STEP3); // // Check for retries. // // retries = (total tries) - 1, so post decrement // when checking. // if(deviceExtension->Mrb.Retries--) { // // If we resubmit the Mrb we need to pet the // watchdog, it wasn't intended to wait through // lots of retries. // MU_DEBUG_PET_WATCHDOG(deviceExtension); // // Resubmit the Mrb // MU_fCbwTransfer(deviceExtension); } else { // // Fail with status unsuccessful // USB_DBG_TRACE_PRINT(("Mrb.CompletionRoutine(0x%0.8x, STATUS_UNSUCCESSFUL)", deviceExtension)); deviceExtension->Mrb.CompletionRoutine(deviceExtension, deviceExtension->Mrb.FailedStatus); } return; default: USB_DBG_ERROR_PRINT(("Reaching here indicates an MU driver bug!")); return; } // // Set the MRB timer to time the reset. // LARGE_INTEGER deltaTime; deltaTime.QuadPart = MRB_RESET_TIMEOUT*MRB_TIMEOUT_UNIT; SET_FLAG(deviceExtension->DeviceFlags,DF_PRIMARY_URB_PENDING); SET_FLAG(deviceExtension->DeviceFlags, DF_MRB_TIMER_RUNNING); KeSetTimer(&deviceExtension->Mrb.Timer, deltaTime, &deviceExtension->Mrb.TimeoutDpcObject); // // Submit the URB built in either case DF_RESET_STEP1, // or case DF_RESET_STEP2. // SET_FLAG(deviceExtension->DeviceFlags,DF_PRIMARY_URB_PENDING); muInstance->Device->SubmitRequest(&deviceExtension->Urb); return; } #if DBG VOID FASTCALL MU_fValidateMrb( IN PMU_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Description: Debug Only routine that validates the current mrb. All this routine does is spew errors or possible errors, otherwise it does not effect the operation of the driver. --*/ { // // 1) If no direction flag is set, then the DataBuffer // and TransferLength are NULL, and 0. // if(!TEST_FLAG(DeviceExtension->Mrb.Flags, MRB_FLAGS_UNSPECIFIED_DIRECTION)) { if(DeviceExtension->Mrb.DataBuffer || DeviceExtension->Mrb.TransferLength) { USB_DBG_ERROR_PRINT(("No direction, but non-NULL DataBuffer or non-zero length")); } } // // 2) Only one direction flag is set. // if(TEST_ALL_FLAGS(DeviceExtension->Mrb.Flags, MRB_FLAGS_UNSPECIFIED_DIRECTION)) { USB_DBG_ERROR_PRINT(("More than one direction flag is set in Mrb!")); } // // 3) If a direction flag is that the DataBuffer is non-NULL // and that the size is finite. // if(TEST_FLAG(DeviceExtension->Mrb.Flags, MRB_FLAGS_UNSPECIFIED_DIRECTION)) { if(NULL == DeviceExtension->Mrb.DataBuffer) { USB_DBG_ERROR_PRINT(("Mrb with transfer to\from NULL DataBuffer!")); } if(0 == DeviceExtension->Mrb.TransferLength) { USB_DBG_ERROR_PRINT(("Mrb with transfer of 0 length!")); } } return; } #endif //DBG