Windows2003-3790/drivers/watchdog/dwd.c

1145 lines
25 KiB
C

/*++
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