/*++ Copyright (c) 2000 Microsoft Corporation Module Name: dwd.c Abstract: This is the NT Watchdog driver implementation. Author: Michael Maciesowicz (mmacie) 05-May-2000 Environment: Kernel mode only. Notes: Revision History: --*/ #include "wd.h" #ifdef ALLOC_PRAGMA #pragma alloc_text (PAGE, WdAllocateDeferredWatchdog) #pragma alloc_text (PAGE, WdFreeDeferredWatchdog) #endif #ifdef WDD_TRACE_ENABLED ULONG g_ulWddIndex = 0; WDD_TRACE g_aWddTrace[WDD_TRACE_SIZE] = {0}; #endif // WDD_TRACE_ENABLED WATCHDOGAPI PDEFERRED_WATCHDOG WdAllocateDeferredWatchdog( IN PDEVICE_OBJECT pDeviceObject, IN WD_TIME_TYPE timeType, IN ULONG ulTag ) /*++ Routine Description: This function allocates storage and initializes a deferred watchdog object. Arguments: pDeviceObject - Points to DEVICE_OBJECT associated with watchdog. timeType - Kernel, User, Both thread time to monitor. ulTag - A tag identifying owner. Return Value: Pointer to allocated deferred watchdog object or NULL. --*/ { PDEFERRED_WATCHDOG pWatch; PAGED_CODE(); ASSERT(NULL != pDeviceObject); ASSERT((timeType >= WdKernelTime) && (timeType <= WdFullTime)); WDD_TRACE_CALL(NULL, WddWdAllocateDeferredWatchdog); // // Allocate storage for deferred watchdog from non-paged pool. // pWatch = (PDEFERRED_WATCHDOG)ExAllocatePoolWithTag(NonPagedPool, sizeof (DEFERRED_WATCHDOG), ulTag); // // Set initial state of deferred watchdog. // if (NULL != pWatch) { // // Set initial state of watchdog. // WdpInitializeObject(pWatch, pDeviceObject, WdDeferredWatchdog, timeType, ulTag); pWatch->Period = 0; pWatch->SuspendCount = 0; pWatch->InCount = 0; pWatch->OutCount = 0; pWatch->LastInCount = 0; pWatch->LastOutCount = 0; pWatch->LastKernelTime = 0; pWatch->LastUserTime = 0; pWatch->TimeIncrement = KeQueryTimeIncrement(); pWatch->Trigger = 0; pWatch->State = WdStopped; pWatch->Thread = NULL; pWatch->ClientDpc = NULL; // // Initialize encapsulated DPC object. // KeInitializeDpc(&(pWatch->TimerDpc), WdpDeferredWatchdogDpcCallback, pWatch); // // Initialize encapsulated timer object. // KeInitializeTimerEx(&(pWatch->Timer), NotificationTimer); } return pWatch; } // WdAllocateDeferredWatchdog() WATCHDOGAPI VOID WdFreeDeferredWatchdog( PDEFERRED_WATCHDOG pWatch ) /*++ Routine Description: This function deallocates storage for deferred watchdog object. It will also stop started deferred watchdog if needed. Arguments: pWatch - Supplies a pointer to a watchdog object. Return Value: None. --*/ { PAGED_CODE(); ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); ASSERT(NULL != pWatch); ASSERT(pWatch->Header.ReferenceCount > 0); WDD_TRACE_CALL(pWatch, WddWdFreeDeferredWatchdog); // // Stop deferred watch just in case somebody forgot. // If the watch is stopped already then this is a no-op. // WdStopDeferredWatch(pWatch); // // Make sure all DPCs on all processors executed to completion. // KeFlushQueuedDpcs(); // // Drop reference count and remove the object if fully dereferenced. // if (InterlockedDecrement(&(pWatch->Header.ReferenceCount)) == 0) { WdpDestroyObject(pWatch); } return; } // WdFreeDeferredWatchdog() WATCHDOGAPI VOID WdStartDeferredWatch( IN PDEFERRED_WATCHDOG pWatch, IN PKDPC pDpc, IN LONG lPeriod ) /*++ Routine Description: This function starts deferred watchdog poller. Arguments: pWatch - Supplies a pointer to a deferred watchdog object. pDpc - Supplies a pointer to a control object of type DPC. ulPeriod - Supplies maximum time in millisecondes that thread can spend in the monitored section. If this time expires a DPC will we queued. Return Value: None. --*/ { KIRQL oldIrql; LARGE_INTEGER liDueTime; #ifdef WD_FAILURE_TEST // // Code to test EA failure handling. To trigger failure set REG_DWORD FailureTest // to watchdog's tag we are interested in and force code path which starts // that watchdog (e.g. switch video mode for 'dwdG' = 0x64776447 tag). // This code should be compiled out for the production version. // ULONG ulFailureTest = 0; ULONG ulDefaultFailureTest = 0; RTL_QUERY_REGISTRY_TABLE queryTable[] = { {NULL, RTL_QUERY_REGISTRY_DIRECT, L"FailureTest", &ulFailureTest, REG_DWORD, &ulDefaultFailureTest, 4}, {NULL, 0, NULL} }; #endif // WD_FAILURE_TEST ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); ASSERT(NULL != pWatch); ASSERT(NULL != pDpc); WDD_TRACE_CALL(pWatch, WddWdStartDeferredWatch); #ifdef WD_FAILURE_TEST RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, WD_KEY_WATCHDOG, queryTable, NULL, NULL); if (ulFailureTest == pWatch->Header.OwnerTag) { RtlDeleteRegistryValue(RTL_REGISTRY_ABSOLUTE, WD_KEY_WATCHDOG, L"FailureTest"); WdpFlushRegistryKey(pWatch, WD_KEY_WATCHDOG); } else { ulFailureTest = 0; } #endif // WD_FAILURE_TEST // // Raise IRQL to dispatcher level and lock dispatcher database. // KeAcquireSpinLock(&(pWatch->Header.SpinLock), &oldIrql); WD_DBG_SUSPENDED_WARNING(pWatch, "WdStartDeferredWatch"); // // We shouldn't hot swap DPCs without stopping first. // ASSERT((NULL == pWatch->ClientDpc) || (pDpc == pWatch->ClientDpc)); pWatch->Period = lPeriod; pWatch->InCount = 0; pWatch->OutCount = 0; pWatch->LastInCount = 0; pWatch->LastOutCount = 0; pWatch->LastKernelTime = 0; pWatch->LastUserTime = 0; pWatch->Trigger = 0; pWatch->State = WdStarted; pWatch->Thread = NULL; pWatch->ClientDpc = pDpc; #ifdef WD_FAILURE_TEST if (ulFailureTest) { // // Force timeout condition. // pWatch->Thread = KeGetCurrentThread(); WdpQueueDeferredEvent(pWatch, WdTimeoutEvent); KeReleaseSpinLock(&(pWatch->Header.SpinLock), oldIrql); return; } #endif // WD_FAILURE_TEST // // Unlock the dispatcher database and lower IRQL to its previous value. // KeReleaseSpinLock(&(pWatch->Header.SpinLock), oldIrql); // // Set first fire to lPeriod. // liDueTime.QuadPart = -(lPeriod * 1000 * 10); KeSetTimerEx(&(pWatch->Timer), liDueTime, lPeriod, &(pWatch->TimerDpc)); return; } // WdStartDeferredWatch() WATCHDOGAPI VOID WdStopDeferredWatch( IN PDEFERRED_WATCHDOG pWatch ) /*++ Routine Description: This function stops deferred watchdog poller. Arguments: pWatch - Supplies a pointer to a watchdog object. Return Value: None. --*/ { KIRQL oldIrql; ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); ASSERT(NULL != pWatch); WDD_TRACE_CALL(pWatch, WddWdStopDeferredWatch); // // Raise IRQL to dispatcher level and lock dispatcher database. // KeAcquireSpinLock(&(pWatch->Header.SpinLock), &oldIrql); WD_DBG_SUSPENDED_WARNING(pWatch, "WdStopDeferredWatch"); if (WdStarted == pWatch->State) { KeCancelTimer(&(pWatch->Timer)); // // Make sure we don't have timeout event pending. // if (NULL != pWatch->ClientDpc) { if (WdTimeoutEvent == pWatch->Header.LastEvent) { KeRemoveQueueDpc(pWatch->ClientDpc); WdpQueueDeferredEvent(pWatch, WdRecoveryEvent); } else if (KeRemoveQueueDpc(pWatch->ClientDpc) == TRUE) { // // Was in queue - call WdCompleteEvent() here since DPC won't be delivered. // WdCompleteEvent(pWatch, pWatch->Header.LastQueuedThread); } } pWatch->Period = 0; pWatch->InCount = 0; pWatch->OutCount = 0; pWatch->LastInCount = 0; pWatch->LastOutCount = 0; pWatch->LastKernelTime = 0; pWatch->LastUserTime = 0; pWatch->Trigger = 0; pWatch->State = WdStopped; pWatch->Thread = NULL; pWatch->ClientDpc = NULL; pWatch->Header.LastQueuedThread = NULL; } // // Unlock the dispatcher database and lower IRQL to its previous value. // KeReleaseSpinLock(&(pWatch->Header.SpinLock), oldIrql); return; } // WdStopDeferredWatch() WATCHDOGAPI VOID FASTCALL WdSuspendDeferredWatch( IN PDEFERRED_WATCHDOG pWatch ) /*++ Routine Description: This function suspends deferred watchdog poller. Arguments: pWatch - Supplies a pointer to a watchdog object. Return Value: None. --*/ { ASSERT(NULL != pWatch); ASSERT((ULONG)(pWatch->SuspendCount) < (ULONG)(-1)); InterlockedIncrement(&(pWatch->SuspendCount)); return; } // WdSuspendDeferredWatch() WATCHDOGAPI VOID FASTCALL WdResumeDeferredWatch( IN PDEFERRED_WATCHDOG pWatch, IN BOOLEAN bIncremental ) /*++ Routine Description: This function resumes deferred watchdog poller. Arguments: pWatch - Supplies a pointer to a watchdog object. bIncremental - If TRUE the watchdog will resume only when SuspendCount reaches 0, if FALSE watchdog resumes immediately and SuspendCount is forced to 0. Return Value: None. --*/ { ASSERT(NULL != pWatch); if (TRUE == bIncremental) { // // Make sure we won't roll under. // if (InterlockedDecrement(&(pWatch->SuspendCount)) == -1) { InterlockedIncrement(&(pWatch->SuspendCount)); } } else { InterlockedExchange(&(pWatch->SuspendCount), 0); } return; } // WdResumeDeferredWatch() WATCHDOGAPI VOID FASTCALL WdResetDeferredWatch( IN PDEFERRED_WATCHDOG pWatch ) /*++ Routine Description: This function resets deferred watchdog poller, i.e. it starts timeout measurement from the scratch if we are in the monitored section. Note: If the watchdog is suspened it will remain suspended. Arguments: pWatch - Supplies a pointer to a watchdog object. Return Value: None. --*/ { KIRQL oldIrql; ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); ASSERT(NULL != pWatch); WDD_TRACE_CALL(pWatch, WddWdResetDeferredWatch); // // Raise IRQL to dispatcher level and lock dispatcher database. // KeAcquireSpinLock(&(pWatch->Header.SpinLock), &oldIrql); pWatch->InCount = 0; pWatch->OutCount = 0; pWatch->Trigger = 0; // // Unlock the dispatcher database and lower IRQL to its previous value. // KeReleaseSpinLock(&(pWatch->Header.SpinLock), oldIrql); return; } // WdResetDeferredWatch() WATCHDOGAPI VOID FASTCALL WdEnterMonitoredSection( IN PDEFERRED_WATCHDOG pWatch ) /*++ Routine Description: This function starts monitoring of the code section for time-out condition. Note: To minimize an overhead it is caller's resposibility to make sure thread remains valid when we are in the monitored section. Arguments: pWatch - Supplies a pointer to a deferred watchdog object. Return Value: None. --*/ { PKTHREAD pThread; KIRQL oldIrql; ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); ASSERT(NULL != pWatch); ASSERT(WdStarted == pWatch->State); // // We have to remove this warning, I hope temporarily, since win32k // is calling this entry point now with suspended watchdog. // // WD_DBG_SUSPENDED_WARNING(pWatch, "WdEnterMonitoredSection"); // pThread = KeGetCurrentThread(); if (pThread != pWatch->Thread) { // // Raise IRQL to dispatcher level and lock dispatcher database. // KeAcquireSpinLock(&(pWatch->Header.SpinLock), &oldIrql); // // We shouldn't swap threads in the monitored section. // ASSERT(pWatch->OutCount == pWatch->InCount); pWatch->Trigger = 0; pWatch->Thread = pThread; // // Unlock the dispatcher database and lower IRQL to its previous value. // KeReleaseSpinLock(&(pWatch->Header.SpinLock), oldIrql); } InterlockedIncrement(&(pWatch->InCount)); return; } // WdEnterMonitoredSection() WATCHDOGAPI VOID FASTCALL WdExitMonitoredSection( IN PDEFERRED_WATCHDOG pWatch ) /*++ Routine Description: This function stops monitoring of the code section for time-out condition. Arguments: pWatch - Supplies a pointer to a deferred watchdog object. Return Value: None. --*/ { ASSERT(NULL != pWatch); ASSERT((pWatch->OutCount < pWatch->InCount) || ((pWatch->OutCount > 0) && (pWatch->InCount < 0))); // // We have to remove this warning, I hope temporarily, since win32k // is calling this entry point now with suspended watchdog. // // WD_DBG_SUSPENDED_WARNING(pWatch, "WdExitMonitoredSection"); // InterlockedIncrement(&(pWatch->OutCount)); return; } // WdExitMonitoredSection() VOID WdpDeferredWatchdogDpcCallback( IN PKDPC pDpc, IN PVOID pDeferredContext, IN PVOID pSystemArgument1, IN PVOID pSystemArgument2 ) /*++ Routine Description: This function is a DPC callback routine for timer object embedded in the deferred watchdog object. It checks thread time and if the wait condition is satisfied it queues original (client) DPC. Arguments: pDpc - Supplies a pointer to a DPC object. pDeferredContext - Supplies a pointer to a deferred watchdog object. pSystemArgument1/2 - Supply time when embedded KTIMER expired. Return Value: None. --*/ { PDEFERRED_WATCHDOG pWatch; LARGE_INTEGER liThreadTime; ULONG ulKernelTime; ULONG ulUserTime; ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); ASSERT(NULL != pDeferredContext); pWatch = (PDEFERRED_WATCHDOG)pDeferredContext; WDD_TRACE_CALL(pWatch, WddWdpDeferredWatchdogDpcCallback); // // Lock dispatcher database. // KeAcquireSpinLockAtDpcLevel(&(pWatch->Header.SpinLock)); if ((WdStarted == pWatch->State) && (NULL != pWatch->Thread)) { switch (pWatch->Trigger) { case 0: // // Everything fine so far, check if we are suspended. // if (pWatch->SuspendCount) { // // We're suspended - do nothing. // break; } // // Check if the last event was a timeout event. // if (WdTimeoutEvent == pWatch->Header.LastEvent) { // // Check if we made any progress. // if ((pWatch->InCount != pWatch->LastInCount) || (pWatch->OutCount != pWatch->LastOutCount) || (pWatch->InCount == pWatch->OutCount)) { // // We recovered - queue recovery event. // WdpQueueDeferredEvent(pWatch, WdRecoveryEvent); } } // // Check if we are in the monitored section. // if (pWatch->InCount == pWatch->OutCount) { // // We're outside monitored section - we're fine. // break; } // // We're inside monitored section - bump up trigger indicator, // and take snapshots of counters and thread's time. // pWatch->Trigger = 1; pWatch->LastInCount = pWatch->InCount; pWatch->LastOutCount = pWatch->OutCount; pWatch->LastKernelTime = KeQueryRuntimeThread(pWatch->Thread, &(pWatch->LastUserTime)); break; case 1: // // We were in the monitored section last time. // // // Check if we're out or suspended. // if ((pWatch->InCount == pWatch->OutCount) || pWatch->SuspendCount) { // // We're outside monitored section or suspended - we're fine. // Reset trigger counter and get out of here. // pWatch->Trigger = 0; break; } // // Check if we made any progress, if so reset snapshots. // if ((pWatch->InCount != pWatch->LastInCount) || (pWatch->OutCount != pWatch->LastOutCount)) { pWatch->Trigger = 1; pWatch->LastInCount = pWatch->InCount; pWatch->LastOutCount = pWatch->OutCount; pWatch->LastKernelTime = KeQueryRuntimeThread(pWatch->Thread, &(pWatch->LastUserTime)); break; } // // Check if we're stuck long enough. // ulKernelTime = KeQueryRuntimeThread(pWatch->Thread, &ulUserTime); switch (pWatch->Header.TimeType) { case WdKernelTime: liThreadTime.QuadPart = ulKernelTime; // // Handle counter rollovers. // if (ulKernelTime < pWatch->LastKernelTime) { liThreadTime.QuadPart += (ULONG)(-1) - pWatch->LastKernelTime + 1; } liThreadTime.QuadPart -= pWatch->LastKernelTime; break; case WdUserTime: liThreadTime.QuadPart = ulUserTime; // // Handle counter rollovers. // if (ulUserTime < pWatch->LastUserTime) { liThreadTime.QuadPart += (ULONG)(-1) - pWatch->LastUserTime + 1; } liThreadTime.QuadPart -= pWatch->LastUserTime; break; case WdFullTime: liThreadTime.QuadPart = ulKernelTime + ulUserTime; // // Handle counter rollovers. // if (ulKernelTime < pWatch->LastKernelTime) { liThreadTime.QuadPart += (ULONG)(-1) - pWatch->LastKernelTime + 1; } if (ulUserTime < pWatch->LastUserTime) { liThreadTime.QuadPart += (ULONG)(-1) - pWatch->LastUserTime + 1; } liThreadTime.QuadPart -= (pWatch->LastKernelTime + pWatch->LastUserTime); break; default: ASSERT(FALSE); liThreadTime.QuadPart = 0; break; } // // Convert to milliseconds. // liThreadTime.QuadPart *= pWatch->TimeIncrement; liThreadTime.QuadPart /= 10000; if (liThreadTime.QuadPart >= pWatch->Period) { // // We've been stuck long enough - queue timeout event. // WdpQueueDeferredEvent(pWatch, WdTimeoutEvent); } break; case 2: // // We have event posted waiting for completion. Nothing to do. // break; default: // // This should never happen. // ASSERT(FALSE); pWatch->Trigger = 0; break; } } // // Unlock the dispatcher database. // KeReleaseSpinLockFromDpcLevel(&(pWatch->Header.SpinLock)); return; } // WdpDeferredWatchdogDpcCallback() BOOLEAN WdpQueueDeferredEvent( IN PDEFERRED_WATCHDOG pWatch, IN WD_EVENT_TYPE eventType ) /*++ Routine Description: This function put watchdog event into client's DPC queue. Arguments: pWatch - Supplies a pointer to a watchdog object. eventType - Watchdog event type to put into client DPC queue. Return Value: TRUE - success, FALSE - failed. Note: Call to this routine must be synchronized by the caller. --*/ { BOOLEAN bStatus; ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL); ASSERT(NULL != pWatch); WDD_TRACE_CALL(pWatch, WddWdpQueueDeferredEvent); // // Preset return value. // bStatus = FALSE; if (NULL != pWatch->ClientDpc) { switch (eventType) { case WdRecoveryEvent: // // We recovered - update event type and queue client DPC. // pWatch->Header.LastEvent = WdRecoveryEvent; // // Bump up references to objects we're going to touch in client DPC. // WdReferenceObject(pWatch); // // Queue client DPC. // // Note: In case of recovery the thread associated with watchdog // object may be deleted by the time we get here. We can't pass it // down to client DPC - we're passing NULL instead. // if (KeInsertQueueDpc(pWatch->ClientDpc, NULL, pWatch) == TRUE) { // // Keep track of qeueued thread in case we cancel this DPC. // pWatch->Header.LastQueuedThread = NULL; // // Make sure we queue DPC only once per event. // pWatch->Trigger = 2; bStatus = TRUE; } else { // // This should never happen. // WdDereferenceObject(pWatch); } break; case WdTimeoutEvent: // // We timed-out - update event type and queue client DPC. // pWatch->Header.LastEvent = WdTimeoutEvent; // // Bump up references to objects we're going to touch in client DPC. // ObReferenceObject(pWatch->Thread); WdReferenceObject(pWatch); // // Queue client DPC. // if (KeInsertQueueDpc(pWatch->ClientDpc, pWatch->Thread, pWatch) == TRUE) { // // Keep track of qeueued thread in case we cancel this DPC. // pWatch->Header.LastQueuedThread = pWatch->Thread; // // Make sure we queue DPC only once per event. // pWatch->Trigger = 2; bStatus = TRUE; } else { // // This should never happen. // ObDereferenceObject(pWatch->Thread); WdDereferenceObject(pWatch); } break; default: // // This should never happen. // ASSERT(FALSE); break; } } return bStatus; } // WdpQueueDeferredEvent() #ifdef WDD_TRACE_ENABLED VOID FASTCALL WddTrace( PDEFERRED_WATCHDOG pWatch, WDD_FUNCTION function ) /*++ Routine Description: This function is used for debugging purposes only to keep track of call sequence. Arguments: pWatch - Supplies a pointer to a watchdog object. function - Enumerator assigned to one of watchdog's routines. Return Value: None. --*/ { static volatile LONG lFlag = 0; static volatile LONG lSpinLockReady = 0; static KSPIN_LOCK spinLock; KIRQL oldIrql; if (InterlockedExchange(&lFlag, 1) == 0) { // // First time - initialize spinlock. // KeInitializeSpinLock(&spinLock); lSpinLockReady = 1; } if (lSpinLockReady) { KeAcquireSpinLock(&spinLock, &oldIrql); if (g_ulWddIndex >= WDD_TRACE_SIZE) { g_ulWddIndex = 0; } g_aWddTrace[g_ulWddIndex].pWatch = pWatch; g_aWddTrace[g_ulWddIndex].function = function; g_ulWddIndex++; KeReleaseSpinLock(&spinLock, oldIrql); } } // WddTrace() #endif // WDD_TRACE_ENABLED