windows-nt/Source/XPSP1/NT/base/fs/rdr2/bowser/bowtimer.c

594 lines
14 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
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
}
}