569 lines
13 KiB
C
569 lines
13 KiB
C
/*++
|
||
|
||
Copyright (c) 1990 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
bowtimer.c
|
||
|
||
Abstract:
|
||
|
||
This module implements all of the timer related routines for the NT
|
||
browser
|
||
|
||
Author:
|
||
|
||
Larry Osterman (LarryO) 21-Jun-1990
|
||
|
||
Revision History:
|
||
|
||
21-Jun-1990 LarryO
|
||
|
||
Created
|
||
|
||
--*/
|
||
|
||
|
||
#include "precomp.h"
|
||
#pragma hdrstop
|
||
|
||
VOID
|
||
BowserTimerDpc(
|
||
IN PKDPC Dpc,
|
||
IN PVOID Context,
|
||
IN PVOID SystemArgument1,
|
||
IN PVOID SystemArgument2
|
||
);
|
||
|
||
VOID
|
||
BowserTimerDispatcher (
|
||
IN PVOID Context
|
||
);
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGE, BowserInitializeTimer)
|
||
#pragma alloc_text(PAGE4BROW, BowserUninitializeTimer)
|
||
#pragma alloc_text(PAGE4BROW, BowserStartTimer)
|
||
#pragma alloc_text(PAGE4BROW, BowserStopTimer)
|
||
#pragma alloc_text(PAGE4BROW, BowserTimerDispatcher)
|
||
#endif
|
||
|
||
VOID
|
||
BowserInitializeTimer(
|
||
IN PBOWSER_TIMER Timer
|
||
)
|
||
{
|
||
PAGED_CODE();
|
||
|
||
KeInitializeTimer(&Timer->Timer);
|
||
|
||
KeInitializeEvent(&Timer->TimerInactiveEvent, NotificationEvent, TRUE);
|
||
|
||
KeInitializeSpinLock(&Timer->Lock);
|
||
|
||
ExInitializeWorkItem(&Timer->WorkItem, BowserTimerDispatcher, Timer);
|
||
|
||
Timer->AlreadySet = FALSE;
|
||
Timer->Canceled = FALSE;
|
||
Timer->SetAgain = FALSE;
|
||
|
||
Timer->Initialized = TRUE;
|
||
}
|
||
|
||
VOID
|
||
BowserUninitializeTimer(
|
||
IN PBOWSER_TIMER Timer
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Prepare the timer for being uninitialized.
|
||
|
||
Arguments:
|
||
IN PBOWSER_TIMER Timer - Timer to stop
|
||
|
||
Return Value
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
KIRQL OldIrql;
|
||
|
||
BowserReferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
DISCARDABLE_CODE( BowserDiscardableCodeSection );
|
||
|
||
ACQUIRE_SPIN_LOCK(&Timer->Lock, &OldIrql);
|
||
|
||
Timer->Initialized = FALSE;
|
||
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
//
|
||
// First stop the timer.
|
||
//
|
||
|
||
BowserStopTimer(Timer);
|
||
|
||
//
|
||
// Now wait to make sure that any the timer routine that is currently
|
||
// executing the timer completes. This allows us to make sure that we
|
||
// never delete a transport while a timer routine is executing.
|
||
//
|
||
|
||
KeWaitForSingleObject(&Timer->TimerInactiveEvent, Executive, KernelMode, FALSE, NULL);
|
||
|
||
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
}
|
||
|
||
// Notes for stopping a timer
|
||
// ==========================
|
||
//
|
||
// Timers run through various states. During some of them they cannot be
|
||
// cancelled. In order to guarantee that we can reliably stop and start
|
||
// timers, they can only be started or stopped at LOW_LEVEL (ie, not from
|
||
// DPC_LEVEL.
|
||
//
|
||
// If the timer is not running then StopTimer does nothing.
|
||
//
|
||
// If queued inside the kernel timer package then KeCancelTimer will work
|
||
// and the timer contents cleaned up.
|
||
//
|
||
// If the kernel timer package has queued the dpc routine then KeCancelTimer
|
||
// will fail. We can flag the timer as canceled. BowserTimerDispatcher will
|
||
// cleanup the timer when it fires.
|
||
//
|
||
|
||
|
||
// Notes for starting a timer
|
||
// ==========================
|
||
//
|
||
// If StartTimer is called on a clean timer then it sets the contents
|
||
// appropriately and gives the timer to the kernel timer package.
|
||
//
|
||
// If the timer is canceled but not cleaned up then StartTimer will update
|
||
// the contents of the timer to show where the new TimerRoutine and TimerContext.
|
||
// it will indicate that the timer is no longer canceled and is now SetAgain.
|
||
//
|
||
// If the timer is already SetAgain then StartTimer will update the contents
|
||
// of the timer to hold the new TimerRoutine and TimerContext.
|
||
//
|
||
// When BowserTimerDispatcher is called on a SetAgain timer, it sets the timer
|
||
// to its normal state and gives the timer to the kernel timer package.
|
||
//
|
||
|
||
|
||
BOOLEAN
|
||
BowserStartTimer(
|
||
IN PBOWSER_TIMER Timer,
|
||
IN ULONG MillisecondsToExpireTimer,
|
||
IN PBOWSER_TIMER_ROUTINE TimerRoutine,
|
||
IN PVOID Context
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Set Timer to call TimerRoutine after MillisecondsToExpire. TimerRoutine
|
||
is to be called at normal level.
|
||
|
||
Arguments:
|
||
|
||
IN PBOWSER_TIMER Timer
|
||
IN ULONG MillisecondsToExpireTimer
|
||
IN PBOWSER_TIMER_ROUTINE TimerRoutine
|
||
IN PVOID Context - Parameter to TimerRoutine
|
||
|
||
Return Value
|
||
BOOLEAN - TRUE if timer set.
|
||
|
||
--*/
|
||
{
|
||
LARGE_INTEGER Timeout;
|
||
BOOLEAN ReturnValue;
|
||
KIRQL OldIrql;
|
||
|
||
BowserReferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
DISCARDABLE_CODE( BowserDiscardableCodeSection );
|
||
|
||
ASSERT (KeGetCurrentIrql() == LOW_LEVEL);
|
||
|
||
Timeout.QuadPart = (LONGLONG)MillisecondsToExpireTimer * (LONGLONG)(-10000);
|
||
// Timeout = LiNMul(MillisecondsToExpireTimer, -10000);
|
||
|
||
ACQUIRE_SPIN_LOCK(&Timer->Lock, &OldIrql);
|
||
|
||
if (!Timer->Initialized) {
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
return(FALSE);
|
||
}
|
||
|
||
dprintf(DPRT_TIMER, ("BowserStartTimer %lx, TimerRoutine %x. Set to expire at %lx%lx (%ld/%ld ms)\n", Timer, TimerRoutine, Timeout.HighPart, Timeout.LowPart, -1 * Timeout.LowPart, MillisecondsToExpireTimer));
|
||
|
||
//
|
||
// We shouldn't be able to start the timer while it is
|
||
// already running unless its also cancelled.
|
||
//
|
||
|
||
|
||
if (Timer->AlreadySet == TRUE) {
|
||
|
||
if (Timer->Canceled) {
|
||
|
||
//
|
||
// This timer has been canceled, but the canceled routine
|
||
// hasn't run yet.
|
||
//
|
||
// Flag that the timer has been re-set, and return to
|
||
// the caller. When the BowserTimerDispatch is finally
|
||
// executed, the new timer will be set.
|
||
//
|
||
|
||
Timer->Timeout = Timeout;
|
||
|
||
Timer->TimerRoutine = TimerRoutine;
|
||
|
||
Timer->SetAgain = TRUE;
|
||
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
return(TRUE);
|
||
|
||
}
|
||
|
||
// BUGBUG: Need to log an error here.
|
||
|
||
InternalError(("Timer started without already being set"));
|
||
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
return(FALSE);
|
||
|
||
}
|
||
|
||
ASSERT (!Timer->Canceled);
|
||
|
||
ASSERT (!Timer->SetAgain);
|
||
|
||
Timer->Timeout = Timeout;
|
||
|
||
Timer->TimerRoutine = TimerRoutine;
|
||
|
||
Timer->TimerContext = Context;
|
||
|
||
Timer->AlreadySet = TRUE;
|
||
|
||
Timer->Canceled = FALSE;
|
||
|
||
Timer->SetAgain = FALSE;
|
||
|
||
//
|
||
// Set the inactive event to the not signalled state to indicate that
|
||
// there is timer activity outstanding.
|
||
//
|
||
|
||
KeResetEvent(&Timer->TimerInactiveEvent);
|
||
|
||
//
|
||
// We are now starting the timer. Initialize the DPC and
|
||
// set the timer.
|
||
//
|
||
|
||
KeInitializeDpc(&Timer->Dpc,
|
||
BowserTimerDpc,
|
||
Timer);
|
||
|
||
ReturnValue = KeSetTimer(&Timer->Timer, Timeout, &Timer->Dpc);
|
||
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
return ReturnValue;
|
||
}
|
||
|
||
|
||
VOID
|
||
BowserStopTimer(
|
||
IN PBOWSER_TIMER Timer
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Stop the timer from calling the TimerRoutine.
|
||
|
||
Arguments:
|
||
IN PBOWSER_TIMER Timer - Timer to stop
|
||
|
||
Return Value
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
KIRQL OldIrql;
|
||
|
||
//
|
||
// Do an unsafe test to see if the timer is already set, we can return.
|
||
//
|
||
|
||
if (!Timer->AlreadySet) {
|
||
return;
|
||
}
|
||
|
||
BowserReferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
DISCARDABLE_CODE( BowserDiscardableCodeSection );
|
||
|
||
//
|
||
// You can only stop a timer at LOW_LEVEL
|
||
//
|
||
|
||
ASSERT (KeGetCurrentIrql() == LOW_LEVEL);
|
||
|
||
ACQUIRE_SPIN_LOCK(&Timer->Lock, &OldIrql);
|
||
|
||
dprintf(DPRT_TIMER, ("BowserStopTimer %lx\n", Timer));
|
||
|
||
//
|
||
// If the timer isn't running, just early out.
|
||
//
|
||
|
||
if (!Timer->AlreadySet) {
|
||
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
return;
|
||
}
|
||
|
||
Timer->Canceled = TRUE;
|
||
|
||
if (!KeCancelTimer(&Timer->Timer)) {
|
||
|
||
//
|
||
// The timer has already fired. It could be in the dpc queue or
|
||
// the work queue. The timer is marked as canceled.
|
||
//
|
||
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
return;
|
||
}
|
||
|
||
//
|
||
// The timer was still in the kernel timer package so we cancelled the
|
||
// timer completely. Return timer to initial state.
|
||
//
|
||
|
||
Timer->AlreadySet = FALSE;
|
||
|
||
//
|
||
// The timer isn't canceled, so it can't be reset.
|
||
//
|
||
|
||
Timer->SetAgain = FALSE;
|
||
|
||
Timer->Canceled = FALSE;
|
||
|
||
KeSetEvent(&Timer->TimerInactiveEvent, IO_NETWORK_INCREMENT, FALSE);
|
||
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
// DbgPrint("Cancel timer %lx complete\n", Timer);
|
||
}
|
||
|
||
VOID
|
||
BowserTimerDpc(
|
||
IN PKDPC Dpc,
|
||
IN PVOID Context,
|
||
IN PVOID SystemArgument1,
|
||
IN PVOID SystemArgument2
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
This routine is called when the timeout expires. It is called at Dpc level
|
||
to queue a WorkItem to a system worker thread.
|
||
|
||
Arguments:
|
||
|
||
IN PKDPC Dpc,
|
||
IN PVOID Context,
|
||
IN PVOID SystemArgument1,
|
||
IN PVOID SystemArgument2
|
||
|
||
Return Value
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
PBOWSER_TIMER Timer = Context;
|
||
|
||
ASSERT (Dpc == &Timer->Dpc);
|
||
|
||
// DbgPrint("Timer %lx fired\n", Timer);
|
||
|
||
ExQueueWorkItem(&Timer->WorkItem, DelayedWorkQueue);
|
||
|
||
UNREFERENCED_PARAMETER(SystemArgument1);
|
||
UNREFERENCED_PARAMETER(SystemArgument2);
|
||
|
||
}
|
||
|
||
VOID
|
||
BowserTimerDispatcher (
|
||
IN PVOID Context
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Call the TimerRoutine and cleanup.
|
||
|
||
Arguments:
|
||
|
||
IN PVOID Context - Original parameter supplied to BowserStartTimer
|
||
|
||
Return Value
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
IN PBOWSER_TIMER Timer = Context;
|
||
|
||
BowserReferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
DISCARDABLE_CODE( BowserDiscardableCodeSection );
|
||
|
||
try {
|
||
KIRQL OldIrql;
|
||
PBOWSER_TIMER_ROUTINE RoutineToCall;
|
||
PVOID ContextForRoutine;
|
||
|
||
ACQUIRE_SPIN_LOCK(&Timer->Lock, &OldIrql);
|
||
|
||
//
|
||
// If the timer was uninitialized, return right away.
|
||
//
|
||
|
||
if (!Timer->Initialized) {
|
||
|
||
dprintf(DPRT_TIMER, ("Timer %lx was uninitialized. Returning.\n", Timer));
|
||
|
||
//
|
||
// Set the inactive event to the signalled state to indicate that
|
||
// the outstanding timer activity has completed.
|
||
//
|
||
|
||
KeSetEvent(&Timer->TimerInactiveEvent, IO_NETWORK_INCREMENT, FALSE);
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
return;
|
||
}
|
||
|
||
if (Timer->Canceled) {
|
||
|
||
dprintf(DPRT_TIMER, ("Timer %lx was cancelled\n", Timer));
|
||
|
||
//
|
||
// If the timer was reset, this indicates that the timer was
|
||
// canceled, but the timer was in the DPC (or executive worker)
|
||
// queue. We want to re-run the timer routine.
|
||
//
|
||
|
||
if (Timer->SetAgain) {
|
||
|
||
ASSERT (Timer->AlreadySet);
|
||
|
||
Timer->SetAgain = FALSE;
|
||
|
||
dprintf(DPRT_TIMER, ("Timer %lx was re-set. Re-setting timer\n", Timer));
|
||
|
||
//
|
||
// We are now starting the timer. Initialize the DPC and
|
||
// set the timer.
|
||
//
|
||
|
||
KeInitializeDpc(&Timer->Dpc,
|
||
BowserTimerDpc,
|
||
Timer);
|
||
|
||
KeSetTimer(&Timer->Timer, Timer->Timeout, &Timer->Dpc);
|
||
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
} else {
|
||
|
||
dprintf(DPRT_TIMER, ("Timer %lx was successfully canceled.\n", Timer));
|
||
|
||
Timer->AlreadySet = FALSE;
|
||
|
||
Timer->Canceled = FALSE;
|
||
|
||
//
|
||
// Set the inactive event to the signalled state to indicate that
|
||
// the outstanding timer activity has completed.
|
||
//
|
||
|
||
KeSetEvent(&Timer->TimerInactiveEvent, IO_NETWORK_INCREMENT, FALSE);
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
}
|
||
|
||
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
return;
|
||
}
|
||
|
||
ASSERT (Timer->AlreadySet);
|
||
|
||
ASSERT (!Timer->SetAgain);
|
||
|
||
Timer->AlreadySet = FALSE;
|
||
|
||
dprintf(DPRT_TIMER, ("Timer %lx fired. Calling %lx\n", Timer, Timer->TimerRoutine));
|
||
|
||
//
|
||
// We release the spinlock so save timer contents locally.
|
||
//
|
||
|
||
RoutineToCall = Timer->TimerRoutine;
|
||
|
||
ContextForRoutine = Timer->TimerContext;
|
||
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
RoutineToCall(ContextForRoutine);
|
||
|
||
ACQUIRE_SPIN_LOCK(&Timer->Lock, &OldIrql);
|
||
if ( !Timer->AlreadySet ) {
|
||
KeSetEvent(&Timer->TimerInactiveEvent, IO_NETWORK_INCREMENT, FALSE);
|
||
}
|
||
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
||
|
||
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
||
|
||
} except (EXCEPTION_EXECUTE_HANDLER) {
|
||
#if DBG
|
||
KdPrint(("BOWSER: Timer routine %lx faulted: %X\n", Timer->TimerRoutine, GetExceptionCode()));
|
||
DbgBreakPoint();
|
||
#else
|
||
KeBugCheck(9999);
|
||
#endif
|
||
}
|
||
|
||
}
|