|
|
/*++
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
BOOL bEnableExceptionBreakpoint = FALSE;
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
LONG BrExceptionFilter( EXCEPTION_POINTERS * pException) { //
// Note: BrExceptionFilter is defined only for checked builds (ifdef DBG)
//
DbgPrint("[Browser] exception 0x%lx.\n", pException->ExceptionRecord->ExceptionCode ); if ( bEnableExceptionBreakpoint && pException->ExceptionRecord->ExceptionCode != STATUS_INSUFFICIENT_RESOURCES && pException->ExceptionRecord->ExceptionCode != STATUS_WORKING_SET_QUOTA ) { DbgBreakPoint(); } return EXCEPTION_EXECUTE_HANDLER; }
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);
}
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);
//
// Due to bug 245645 we need to queue in delayed worker queue rather then execute timed tasks.
// OLD WAY: ExQueueWorkItem(&Timer->WorkItem, DelayedWorkQueue);
//
BowserQueueDelayedWorkItem( &Timer->WorkItem );
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 (BR_EXCEPTION) { #if DBG
KdPrint(("BOWSER: Timer routine %lx faulted: %X\n", Timer->TimerRoutine, GetExceptionCode())); DbgBreakPoint(); #else
KeBugCheck(9999); #endif
}
}
|