/*++ Copyright (c) 1999 Microsoft Corporation Module Name: dmtimer.c Abstract: This module implements our 'deadman' timer DPC. This is our general purpose timer we use to deal with situations where the controller is not giving us interrupts. examples: root hub polling. dead controller detection Environment: kernel mode only Notes: Revision History: 1-1-00 : created --*/ #include "common.h" // paged functions #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, USBPORT_StartDM_Timer) #endif // non paged functions // USBPORT_DM_TimerDpc // USBPORT_StopDM_Timer VOID USBPORT_DM_TimerDpc( PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2 ) /*++ Routine Description: This routine runs at DISPATCH_LEVEL IRQL. Arguments: Dpc - Pointer to the DPC object. DeferredContext - supplies FdoDeviceObject. SystemArgument1 - not used. SystemArgument2 - not used. Return Value: None. --*/ { PDEVICE_OBJECT fdoDeviceObject = DeferredContext; PDEVICE_EXTENSION devExt; BOOLEAN setTimer; KIRQL irql; GET_DEVICE_EXT(devExt, fdoDeviceObject); ASSERT_FDOEXT(devExt); #if DBG { LARGE_INTEGER t; KeQuerySystemTime(&t); LOGENTRY(NULL, fdoDeviceObject, LOG_NOISY, 'dmTM', fdoDeviceObject, t.LowPart, 0); } #endif // if stop fires it will stall here // if stop is running we stall here USBPORT_ACQUIRE_DM_LOCK(devExt, irql); #ifdef XPSE // poll while suspended if (TEST_FDO_FLAG(devExt, USBPORT_FDOFLAG_SUSPENDED) && TEST_FDO_FLAG(devExt, USBPORT_FDOFLAG_POLL_IN_SUSPEND)) { MP_CheckController(devExt); if (!TEST_FDO_FLAG(devExt,USBPORT_FDOFLAG_CONTROLLER_GONE)) { MP_PollController(devExt); } } #endif USBPORT_SynchronizeControllersStart(fdoDeviceObject); // skip timer is set when we are in low power if (TEST_FDO_FLAG(devExt, USBPORT_FDOFLAG_SKIP_TIMER_WORK)) { // some work we should always do // for an upcomming bug fix USBPORT_BadRequestFlush(fdoDeviceObject, FALSE); } else { MP_CheckController(devExt); if (TEST_FDO_FLAG(devExt, USBPORT_FDOFLAG_POLL_CONTROLLER) && !TEST_FDO_FLAG(devExt,USBPORT_FDOFLAG_CONTROLLER_GONE)) { MP_PollController(devExt); } // call the ISR worker here in the event that the controller // is unable to generate interrupts USBPORT_IsrDpcWorker(fdoDeviceObject, FALSE); USBPORT_TimeoutAllEndpoints(fdoDeviceObject); // invalidate all isochronous endpoints // flush async requests USBPORT_BadRequestFlush(fdoDeviceObject, FALSE); } setTimer = TEST_FDO_FLAG(devExt, USBPORT_FDOFLAG_DM_TIMER_ENABLED); USBPORT_RELEASE_DM_LOCK(devExt, irql); if (setTimer) { ULONG timerIncerent; LONG dueTime; timerIncerent = KeQueryTimeIncrement() - 1; // round up to the next highest timer increment dueTime= -1 * (MILLISECONDS_TO_100_NS_UNITS(devExt->Fdo.DM_TimerInterval) + timerIncerent); KeSetTimer(&devExt->Fdo.DM_Timer, RtlConvertLongToLargeInteger(dueTime), &devExt->Fdo.DM_TimerDpc); INCREMENT_PENDING_REQUEST_COUNT(fdoDeviceObject, NULL); } // this timer is done DECREMENT_PENDING_REQUEST_COUNT(fdoDeviceObject, NULL); } // BUGBUG HP ia64 fix VOID USBPORT_DoRootHubCallback( PDEVICE_OBJECT FdoDeviceObject, PDEVICE_OBJECT Usb2Fdo ) /*++ Routine Description: Execute the root hub notifaction callback -- called from the worker thread Arguments: FdoDeviceObject - FDO device object for a USB 1.1 controller that may be a CC Return Value: None. --*/ { PDEVICE_EXTENSION devExt, rhDevExt; PRH_INIT_CALLBACK cb; PVOID context; KIRQL irql; GET_DEVICE_EXT(devExt, FdoDeviceObject); ASSERT_FDOEXT(devExt); GET_DEVICE_EXT(rhDevExt, devExt->Fdo.RootHubPdo); ASSERT_PDOEXT(rhDevExt); KeAcquireSpinLock(&devExt->Fdo.HcSyncSpin.sl, &irql); USBPORT_ASSERT(rhDevExt->Pdo.HubInitCallback != NULL); LOGENTRY(NULL, FdoDeviceObject, LOG_PNP, 'syCB', rhDevExt, 0, 0); context = rhDevExt->Pdo.HubInitContext; cb = rhDevExt->Pdo.HubInitCallback; rhDevExt->Pdo.HubInitCallback = NULL; rhDevExt->Pdo.HubInitContext = NULL; KeReleaseSpinLock(&devExt->Fdo.HcSyncSpin.sl, irql); // root hub list should be empty #if DBG { PHCD_ENDPOINT ep = rhDevExt->Pdo.RootHubInterruptEndpoint; USBPORT_ASSERT(IsListEmpty(&ep->ActiveList)); USBPORT_ASSERT(IsListEmpty(&ep->PendingList)); } #endif // perform High speed chirp now. The EHCI driver is started and the CC // must also be started since the root hub has registered a callback // this will prevent the controller from going into suspend right away // for lack of devices. if (Usb2Fdo) { PDEVICE_EXTENSION usb2DevExt; GET_DEVICE_EXT(usb2DevExt, Usb2Fdo); ASSERT_FDOEXT(usb2DevExt); // note in .NET this function takes the CC FDO // and aquires the CC lock // the USB 2 controller may already be in suspend if the CC root hub is // being added later, for this case we skip the chirp. if (!TEST_FDO_FLAG(usb2DevExt, USBPORT_FDOFLAG_SUSPENDED)) { USBPORT_RootHub_PowerAndChirpAllCcPorts(FdoDeviceObject); } } cb(context); } VOID USBPORT_SynchronizeControllersStart( PDEVICE_OBJECT FdoDeviceObject ) /*++ Routine Description: see if it is OK to start a controllers root hub Arguments: FdoDeviceObject - FDO device object for a USB 1.1 controller that may be a CC Return Value: None. --*/ { PDEVICE_EXTENSION devExt, rhDevExt; KIRQL irql; PRH_INIT_CALLBACK cb; PVOID context; BOOLEAN okToStart; PDEVICE_OBJECT usb2Fdo = NULL; PDEVICE_EXTENSION usb2DevExt; GET_DEVICE_EXT(devExt, FdoDeviceObject); ASSERT_FDOEXT(devExt); // skip if we don't have a root hub if (devExt->Fdo.RootHubPdo == NULL) { return; } GET_DEVICE_EXT(rhDevExt, devExt->Fdo.RootHubPdo); ASSERT_PDOEXT(rhDevExt); // if no callback registered skip the whole process if (rhDevExt->Pdo.HubInitCallback == NULL) { return; } // if callback pending on worker thread skip if (TEST_FDO_FLAG(devExt, USBPORT_FDOFLAG_SIGNAL_RH)) { return; } LOGENTRY(NULL, FdoDeviceObject, LOG_PNP, 'syn1', FdoDeviceObject, 0, 0); if (TEST_FDO_FLAG(devExt, USBPORT_FDOFLAG_IS_CC)) { okToStart = FALSE; // // we need to find the 2.0 master controller, // if the hub has started then it is OK to // start. usb2Fdo = USBPORT_FindUSB2Controller(FdoDeviceObject); LOGENTRY(NULL, FdoDeviceObject, LOG_PNP, 'syn2', 0, 0, usb2Fdo); if (usb2Fdo != NULL) { GET_DEVICE_EXT(usb2DevExt, usb2Fdo); ASSERT_FDOEXT(usb2DevExt); if (TEST_FLAG(usb2DevExt->PnpStateFlags, USBPORT_PNP_STARTED)) { LOGENTRY(NULL, FdoDeviceObject, LOG_PNP, 'syn3', 0, 0, usb2Fdo); okToStart = TRUE; } } // is companion but check if OK to bypass the wait if (TEST_FDO_FLAG(devExt, USBPORT_FDOFLAG_CC_ENUM_OK)) { okToStart = TRUE; } } else { // not a CC, it is OK to start immediatly LOGENTRY(NULL, FdoDeviceObject, LOG_PNP, 'syn4', 0, 0, 0); okToStart = TRUE; } // check for a start-hub callback notification. If we have // one then notify the hub that it is OK if (okToStart) { if (usb2Fdo) { GET_DEVICE_EXT(usb2DevExt, usb2Fdo); // signal the worker to chirp and do the callback SET_FDO_FLAG(devExt, USBPORT_FDOFLAG_SIGNAL_RH); InterlockedIncrement(&usb2DevExt->Fdo.PendingRhCallback); LOGENTRY(NULL, FdoDeviceObject, LOG_PNP, 'prh+', 0, 0, usb2DevExt->Fdo.PendingRhCallback); USBPORT_SignalWorker(FdoDeviceObject); } else { // no 2.0 controller just callback now USBPORT_DoRootHubCallback(FdoDeviceObject, NULL); } } } VOID USBPORT_BadRequestFlush( PDEVICE_OBJECT FdoDeviceObject, BOOLEAN ForceFlush ) /*++ Routine Description: Asynchronously flush bad requests from client drivers Arguments: Return Value: None. --*/ { PDEVICE_EXTENSION devExt; KIRQL irql; GET_DEVICE_EXT(devExt, FdoDeviceObject); ASSERT_FDOEXT(devExt); // The purpose of this async flush is to emulate the Win2k // behavior where we put tranfers on the HW to devices that // had been removed and let them time out. // // origanlly I used 5 here to fix problems with hid drivers // on win2k+usb20, we may need to change this depending on // which OS we are running on, we want use a smaller value on // XP since HID (the major offender) has been fixed and many // of our other in house class drivers support hot remove // better. #define BAD_REQUEST_FLUSH 0 devExt->Fdo.BadRequestFlush++; if (devExt->Fdo.BadRequestFlush > devExt->Fdo.BadReqFlushThrottle || ForceFlush) { devExt->Fdo.BadRequestFlush = 0; // flush and complete any 'bad parameter' requests ACQUIRE_BADREQUEST_LOCK(FdoDeviceObject, irql); while (1) { PLIST_ENTRY listEntry; PIRP irp; PUSB_IRP_CONTEXT irpContext; NTSTATUS ntStatus; if (IsListEmpty(&devExt->Fdo.BadRequestList)) { break; } listEntry = RemoveHeadList(&devExt->Fdo.BadRequestList); //irp = (PIRP) CONTAINING_RECORD( // listEntry, // struct _IRP, // Tail.Overlay.ListEntry); irpContext = (PUSB_IRP_CONTEXT) CONTAINING_RECORD( listEntry, struct _USB_IRP_CONTEXT, ListEntry); ASSERT_IRP_CONTEXT(irpContext); irp = irpContext->Irp; if (irp->Cancel) { ntStatus = STATUS_CANCELLED; } else { ntStatus = STATUS_DEVICE_NOT_CONNECTED; } RELEASE_BADREQUEST_LOCK(FdoDeviceObject, irql); // cancel routine did not run LOGENTRY(NULL, FdoDeviceObject, LOG_IRPS, 'cpBA', irp, irpContext, 0); USBPORT_CompleteIrp(devExt->Fdo.RootHubPdo, irp, ntStatus, 0); FREE_POOL(FdoDeviceObject, irpContext); ACQUIRE_BADREQUEST_LOCK(FdoDeviceObject, irql); } RELEASE_BADREQUEST_LOCK(FdoDeviceObject, irql); } } VOID USBPORT_StartDM_Timer( PDEVICE_OBJECT FdoDeviceObject, LONG MillisecondInterval ) /*++ Routine Description: Inialize and start the timer Arguments: FdoDeviceObject - DeviceObject of the controller to stop MillisecondInterval - Return Value: None. --*/ { PDEVICE_EXTENSION devExt; LONG dueTime; ULONG timerIncerent; PAGED_CODE(); GET_DEVICE_EXT(devExt, FdoDeviceObject); ASSERT_FDOEXT(devExt); timerIncerent = KeQueryTimeIncrement() - 1; // remember interval for repeated use devExt->Fdo.DM_TimerInterval = MillisecondInterval; // round up to the next highest timer increment dueTime= -1 * (MILLISECONDS_TO_100_NS_UNITS(MillisecondInterval) + timerIncerent); USBPORT_KdPrint((1, " DM timer (100ns) = %d\n", dueTime)); SET_FDO_FLAG(devExt, USBPORT_FDOFLAG_DM_TIMER_ENABLED); // we consider the timer a pending request while it is // scheduled INCREMENT_PENDING_REQUEST_COUNT(FdoDeviceObject, NULL); KeInitializeTimer(&devExt->Fdo.DM_Timer); KeInitializeDpc(&devExt->Fdo.DM_TimerDpc, USBPORT_DM_TimerDpc, FdoDeviceObject); KeSetTimer(&devExt->Fdo.DM_Timer, RtlConvertLongToLargeInteger(dueTime), &devExt->Fdo.DM_TimerDpc); SET_FDO_FLAG(devExt, USBPORT_FDOFLAG_DM_TIMER_INIT); } VOID USBPORT_StopDM_Timer( PDEVICE_OBJECT FdoDeviceObject ) /*++ Routine Description: stop the timer Arguments: FdoDeviceObject - DeviceObject of the controller to stop Return Value: None. --*/ { PDEVICE_EXTENSION devExt; KIRQL irql; BOOLEAN inQueue; GET_DEVICE_EXT(devExt, FdoDeviceObject); ASSERT_FDOEXT(devExt); if (!TEST_FDO_FLAG(devExt, USBPORT_FDOFLAG_DM_TIMER_INIT)) { // timer never started so bypass the stop return; } // if timer fires it will stall here // if timer is running we will stall here USBPORT_ACQUIRE_DM_LOCK(devExt, irql); USBPORT_ASSERT(TEST_FDO_FLAG(devExt, USBPORT_FDOFLAG_DM_TIMER_ENABLED)); LOGENTRY(NULL, FdoDeviceObject, LOG_MISC, 'kilT', FdoDeviceObject, 0, 0); CLEAR_FDO_FLAG(devExt, USBPORT_FDOFLAG_DM_TIMER_ENABLED); // timer will no longer re-schedule USBPORT_RELEASE_DM_LOCK(devExt, irql); // if there is a timer in the queue, remove it inQueue = KeCancelTimer(&devExt->Fdo.DM_Timer); if (inQueue) { // it was queue, so dereference now LOGENTRY(NULL, FdoDeviceObject, LOG_MISC, 'klIQ', FdoDeviceObject, 0, 0); DECREMENT_PENDING_REQUEST_COUNT(FdoDeviceObject, NULL); } CLEAR_FDO_FLAG(devExt, USBPORT_FDOFLAG_DM_TIMER_INIT); }