|
|
/*++
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)
#endif
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));
//
// 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.
//
WdInitializeObject(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->Started = FALSE; pWatch->Thread = NULL; pWatch->ClientDpc = NULL;
//
// Initialize encapsulated DPC object.
//
KeInitializeDpc(&(pWatch->TimerDpc), WdDeferredWatchdogDpcCallback, 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.
--*/
{ ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); ASSERT(NULL != pWatch); ASSERT(pWatch->Header.ReferenceCount > 0);
//
// Stop deferred watch just in case somebody forgot.
// If the watch is stopped already then this is a no-op.
//
WdStopDeferredWatch(pWatch);
//
// Drop reference count and remove the object if fully dereferenced.
//
if (InterlockedDecrement(&(pWatch->Header.ReferenceCount)) == 0) { WdRemoveObject(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;
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); ASSERT(NULL != pWatch); ASSERT(NULL != pDpc);
//
// 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->Started = TRUE; pWatch->Thread = NULL; pWatch->ClientDpc = pDpc;
//
// 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);
//
// Raise IRQL to dispatcher level and lock dispatcher database.
//
KeAcquireSpinLock(&(pWatch->Header.SpinLock), &oldIrql);
WD_DBG_SUSPENDED_WARNING(pWatch, "WdStopDeferredWatch");
if (TRUE == pWatch->Started) { KeCancelTimer(&(pWatch->Timer));
//
// Make sure we don't have client's DPC pending.
//
if (NULL != pWatch->ClientDpc) { 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->Started = FALSE; pWatch->Thread = NULL; pWatch->ClientDpc = NULL; pWatch->Header.LastEvent = WdNoEvent; 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; } // WdSuspendDeferredWatch()
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);
//
// 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(pWatch->Started);
//
// 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 WdDeferredWatchdogDpcCallback( 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;
//
// Lock dispatcher database.
//
KeAcquireSpinLockAtDpcLevel(&(pWatch->Header.SpinLock));
if (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 - update event type and queue client DPC.
//
pWatch->Header.LastEvent = WdRecoveryEvent;
if (NULL != pWatch->ClientDpc) { //
// 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; } else { //
// This should never happen.
//
WdDereferenceObject(pWatch); } } } }
//
// 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 - update event type and queue client DPC.
//
pWatch->Header.LastEvent = WdTimeoutEvent;
if (NULL != pWatch->ClientDpc) { //
// 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; } else { //
// This should never happen.
//
ObDereferenceObject(pWatch->Thread); WdDereferenceObject(pWatch); } } }
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; } // WdDeferredWatchdogDpcCallback()
|