585 lines
13 KiB
C
585 lines
13 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1989 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
timerobj.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
This module implements the kernel timer object. Functions are
|
|||
|
provided to initialize, read, set, and cancel timer objects.
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
David N. Cutler (davec) 2-Mar-1989
|
|||
|
|
|||
|
Environment:
|
|||
|
|
|||
|
Kernel mode only.
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
#include "ki.h"
|
|||
|
|
|||
|
#ifdef ALLOC_PRAGMA
|
|||
|
#pragma alloc_text(PAGELK, KeQueryTimerDueTime)
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// The following assert macro is used to check that an input timer is
|
|||
|
// really a ktimer and not something else, like deallocated pool.
|
|||
|
//
|
|||
|
|
|||
|
#define ASSERT_TIMER(E) { \
|
|||
|
ASSERT(((E)->Header.Type == TimerNotificationObject) || \
|
|||
|
((E)->Header.Type == TimerSynchronizationObject)); \
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
KeInitializeTimer (
|
|||
|
IN PKTIMER Timer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function initializes a kernel timer object.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Timer - Supplies a pointer to a dispatcher object of type timer.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
|
|||
|
//
|
|||
|
// Initialize extended timer object with a type of notification and a
|
|||
|
// period of zero.
|
|||
|
//
|
|||
|
|
|||
|
KeInitializeTimerEx(Timer, NotificationTimer);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
KeInitializeTimerEx (
|
|||
|
IN PKTIMER Timer,
|
|||
|
IN TIMER_TYPE Type
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function initializes an extended kernel timer object.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Timer - Supplies a pointer to a dispatcher object of type timer.
|
|||
|
|
|||
|
Type - Supplies the type of timer object; NotificationTimer or
|
|||
|
SynchronizationTimer;
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
//
|
|||
|
// Initialize standard dispatcher object header and set initial
|
|||
|
// state of timer.
|
|||
|
//
|
|||
|
|
|||
|
Timer->Header.Type = TimerNotificationObject + Type;
|
|||
|
Timer->Header.Inserted = FALSE;
|
|||
|
Timer->Header.Size = sizeof(KTIMER) / sizeof(LONG);
|
|||
|
Timer->Header.SignalState = FALSE;
|
|||
|
|
|||
|
#if DBG
|
|||
|
|
|||
|
Timer->TimerListEntry.Flink = NULL;
|
|||
|
Timer->TimerListEntry.Blink = NULL;
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
InitializeListHead(&Timer->Header.WaitListHead);
|
|||
|
Timer->DueTime.QuadPart = 0;
|
|||
|
Timer->Period = 0;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
KeClearTimer (
|
|||
|
IN PKTIMER Timer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function clears the signal state of an timer object.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Event - Supplies a pointer to a dispatcher object of type timer.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
|
|||
|
ASSERT_TIMER(Timer);
|
|||
|
|
|||
|
//
|
|||
|
// Clear signal state of timer object.
|
|||
|
//
|
|||
|
|
|||
|
Timer->Header.SignalState = 0;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
KeCancelTimer (
|
|||
|
IN PKTIMER Timer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function cancels a timer that was previously set to expire at
|
|||
|
a specified time. If the timer is not currently set, then no operation
|
|||
|
is performed. Canceling a timer does not set the state of the timer to
|
|||
|
Signaled.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Timer - Supplies a pointer to a dispatcher object of type timer.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
A boolean value of TRUE is returned if the the specified timer was
|
|||
|
currently set. Else a value of FALSE is returned.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
|
|||
|
BOOLEAN Inserted;
|
|||
|
KIRQL OldIrql;
|
|||
|
|
|||
|
ASSERT_TIMER(Timer);
|
|||
|
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
|
|||
|
|
|||
|
//
|
|||
|
// Raise IRQL to dispatcher level, lock the dispatcher database, and
|
|||
|
// capture the timer inserted status. If the timer is currently set,
|
|||
|
// then remove it from the timer list.
|
|||
|
//
|
|||
|
|
|||
|
KiLockDispatcherDatabase(&OldIrql);
|
|||
|
Inserted = Timer->Header.Inserted;
|
|||
|
if (Inserted != FALSE) {
|
|||
|
KiRemoveTreeTimer(Timer);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Unlock the dispatcher database, lower IRQL to its previous value, and
|
|||
|
// return boolean value that signifies whether the timer was set of not.
|
|||
|
//
|
|||
|
|
|||
|
KiUnlockDispatcherDatabase(OldIrql);
|
|||
|
return Inserted;
|
|||
|
}
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
KeReadStateTimer (
|
|||
|
IN PKTIMER Timer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function reads the current signal state of a timer object.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Timer - Supplies a pointer to a dispatcher object of type timer.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
The current signal state of the timer object.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
|
|||
|
ASSERT_TIMER(Timer);
|
|||
|
|
|||
|
//
|
|||
|
// Return current signal state of timer object.
|
|||
|
//
|
|||
|
|
|||
|
return (BOOLEAN)Timer->Header.SignalState;
|
|||
|
}
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
KeSetTimer (
|
|||
|
IN PKTIMER Timer,
|
|||
|
IN LARGE_INTEGER DueTime,
|
|||
|
IN PKDPC Dpc OPTIONAL
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function sets a timer to expire at a specified time. If the timer is
|
|||
|
already set, then it is implicitly canceled before it is set to expire at
|
|||
|
the specified time. Setting a timer causes its due time to be computed,
|
|||
|
its state to be set to Not-Signaled, and the timer object itself to be
|
|||
|
inserted in the timer list.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Timer - Supplies a pointer to a dispatcher object of type timer.
|
|||
|
|
|||
|
DueTime - Supplies an absolute or relative time at which the timer
|
|||
|
is to expire.
|
|||
|
|
|||
|
Dpc - Supplies an optional pointer to a control object of type DPC.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
A boolean value of TRUE is returned if the the specified timer was
|
|||
|
currently set. Else a value of FALSE is returned.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
|
|||
|
//
|
|||
|
// Set the timer with a period of zero.
|
|||
|
//
|
|||
|
|
|||
|
return KeSetTimerEx(Timer, DueTime, 0, Dpc);
|
|||
|
}
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
KeSetTimerEx (
|
|||
|
IN PKTIMER Timer,
|
|||
|
IN LARGE_INTEGER DueTime,
|
|||
|
IN LONG Period OPTIONAL,
|
|||
|
IN PKDPC Dpc OPTIONAL
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function sets a timer to expire at a specified time. If the timer is
|
|||
|
already set, then it is implicitly canceled before it is set to expire at
|
|||
|
the specified time. Setting a timer causes its due time to be computed,
|
|||
|
its state to be set to Not-Signaled, and the timer object itself to be
|
|||
|
inserted in the timer list.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Timer - Supplies a pointer to a dispatcher object of type timer.
|
|||
|
|
|||
|
DueTime - Supplies an absolute or relative time at which the timer
|
|||
|
is to expire.
|
|||
|
|
|||
|
Period - Supplies an optional period for the timer in milliseconds.
|
|||
|
|
|||
|
Dpc - Supplies an optional pointer to a control object of type DPC.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
A boolean value of TRUE is returned if the the specified timer was
|
|||
|
currently set. Else a value of FALSE is returned.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
|
|||
|
BOOLEAN Inserted;
|
|||
|
LARGE_INTEGER Interval;
|
|||
|
KIRQL OldIrql;
|
|||
|
LARGE_INTEGER SystemTime;
|
|||
|
|
|||
|
ASSERT_TIMER(Timer);
|
|||
|
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
|
|||
|
|
|||
|
//
|
|||
|
// Raise IRQL to dispatcher level and lock dispatcher database.
|
|||
|
//
|
|||
|
|
|||
|
KiLockDispatcherDatabase(&OldIrql);
|
|||
|
|
|||
|
//
|
|||
|
// Capture the timer inserted status and if the timer is currently
|
|||
|
// set, then remove it from the timer list.
|
|||
|
//
|
|||
|
|
|||
|
Inserted = Timer->Header.Inserted;
|
|||
|
if (Inserted != FALSE) {
|
|||
|
KiRemoveTreeTimer(Timer);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Clear the signal state, set the period, set the DPC address, and
|
|||
|
// insert the timer in the timer tree. If the timer is not inserted
|
|||
|
// in the timer tree, then it has already expired and as many waiters
|
|||
|
// as possible should be continued, and a DPC, if specified should be
|
|||
|
// queued.
|
|||
|
//
|
|||
|
// N.B. The signal state must be cleared in case the period is not
|
|||
|
// zero.
|
|||
|
//
|
|||
|
|
|||
|
Timer->Header.SignalState = FALSE;
|
|||
|
Timer->Dpc = Dpc;
|
|||
|
Timer->Period = Period;
|
|||
|
if (KiInsertTreeTimer((PRKTIMER)Timer, DueTime) == FALSE) {
|
|||
|
if (IsListEmpty(&Timer->Header.WaitListHead) == FALSE) {
|
|||
|
KiWaitTest(Timer, TIMER_EXPIRE_INCREMENT);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If a DPC is specfied, then call the DPC routine.
|
|||
|
//
|
|||
|
|
|||
|
if (Dpc != NULL) {
|
|||
|
KiQuerySystemTime(&SystemTime);
|
|||
|
KeInsertQueueDpc(Timer->Dpc,
|
|||
|
ULongToPtr(SystemTime.LowPart),
|
|||
|
ULongToPtr(SystemTime.HighPart));
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the timer is periodic, then compute the next interval time
|
|||
|
// and reinsert the timer in the timer tree.
|
|||
|
//
|
|||
|
// N.B. Even though the timer insertion is relative, it can still
|
|||
|
// fail if the period of the timer elapses in between computing
|
|||
|
// the time and inserting the timer. If this happens, then the
|
|||
|
// the insertion is retried.
|
|||
|
//
|
|||
|
|
|||
|
if (Period != 0) {
|
|||
|
Interval.QuadPart = Int32x32To64(Timer->Period, - 10 * 1000);
|
|||
|
do {
|
|||
|
} while (KiInsertTreeTimer(Timer, Interval) == FALSE);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Unlock the dispatcher database and lower IRQL to its previous
|
|||
|
// value.
|
|||
|
//
|
|||
|
|
|||
|
KiUnlockDispatcherDatabase(OldIrql);
|
|||
|
|
|||
|
//
|
|||
|
// Return boolean value that signifies whether the timer was set of
|
|||
|
// not.
|
|||
|
//
|
|||
|
|
|||
|
return Inserted;
|
|||
|
}
|
|||
|
|
|||
|
ULONGLONG
|
|||
|
KeQueryTimerDueTime (
|
|||
|
IN PKTIMER Timer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function returns the InterruptTime at which the timer is
|
|||
|
pending. 0 is returned if the timer is not pending.
|
|||
|
|
|||
|
N.B. This function may only be called by the system sleep code.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Timer - Supplies a pointer to a dispatcher object of type timer.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Returns the amount of time remaining on the timer, or 0 if the
|
|||
|
timer is not pending.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
|
|||
|
KIRQL OldIrql;
|
|||
|
LARGE_INTEGER InterruptTime;
|
|||
|
ULONGLONG DueTime;
|
|||
|
|
|||
|
ASSERT_TIMER(Timer);
|
|||
|
|
|||
|
//
|
|||
|
// Raise IRQL to dispatcher level and lock dispatcher database.
|
|||
|
//
|
|||
|
|
|||
|
KiLockDispatcherDatabase(&OldIrql);
|
|||
|
|
|||
|
//
|
|||
|
// If the timer is currently pending, compute its due time
|
|||
|
//
|
|||
|
|
|||
|
DueTime = 0;
|
|||
|
if (Timer->Header.Inserted) {
|
|||
|
DueTime = Timer->DueTime.QuadPart;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Unlock the dispatcher database and lower IRQL to its previous
|
|||
|
// value, and return the due time
|
|||
|
//
|
|||
|
|
|||
|
KiUnlockDispatcherDatabase(OldIrql);
|
|||
|
return DueTime;
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
KeCheckForTimer(
|
|||
|
IN PVOID BlockStart,
|
|||
|
IN SIZE_T BlockSize
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function is used for debugging by checking all timers
|
|||
|
to see if any is in the memory block passed. If so, the
|
|||
|
system bugchecks.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
BlockStart - Base address to check for timer.
|
|||
|
|
|||
|
BlockSize - Size (in bytes) to check in the memory block.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
ULONG Index;
|
|||
|
PLIST_ENTRY ListHead;
|
|||
|
PLIST_ENTRY NextEntry;
|
|||
|
KIRQL OldIrql;
|
|||
|
PKTIMER Timer;
|
|||
|
PUCHAR Address;
|
|||
|
PUCHAR Start;
|
|||
|
PUCHAR End;
|
|||
|
|
|||
|
//
|
|||
|
// Compute the ending memory location.
|
|||
|
//
|
|||
|
|
|||
|
Start = (PUCHAR)BlockStart;
|
|||
|
End = Start + BlockSize;
|
|||
|
|
|||
|
//
|
|||
|
// Raise IRQL to dispatcher level and lock dispatcher database.
|
|||
|
//
|
|||
|
|
|||
|
KiLockDispatcherDatabase(&OldIrql);
|
|||
|
|
|||
|
//
|
|||
|
// Run the entire timer database and check for any timers in
|
|||
|
// the memory block
|
|||
|
//
|
|||
|
|
|||
|
Index = 0;
|
|||
|
do {
|
|||
|
ListHead = &KiTimerTableListHead[Index];
|
|||
|
NextEntry = ListHead->Flink;
|
|||
|
while (NextEntry != ListHead) {
|
|||
|
Timer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
|
|||
|
Address = (PUCHAR)Timer;
|
|||
|
NextEntry = NextEntry->Flink;
|
|||
|
|
|||
|
//
|
|||
|
// Check this timer object is not in the range.
|
|||
|
// In each of the following, we check that the object
|
|||
|
// does not overlap the range, for example, if the timer
|
|||
|
// object (in this first check), starts one dword before
|
|||
|
// the range being checked, we have an overlap and should
|
|||
|
// stop.
|
|||
|
//
|
|||
|
|
|||
|
if ((Address > (Start - sizeof(KTIMER))) &&
|
|||
|
(Address < End)) {
|
|||
|
KeBugCheckEx(TIMER_OR_DPC_INVALID,
|
|||
|
0x0,
|
|||
|
(ULONG_PTR)Address,
|
|||
|
(ULONG_PTR)Start,
|
|||
|
(ULONG_PTR)End);
|
|||
|
}
|
|||
|
|
|||
|
if (Timer->Dpc) {
|
|||
|
|
|||
|
//
|
|||
|
// Check the timer's DPC object isn't in the range.
|
|||
|
//
|
|||
|
|
|||
|
Address = (PUCHAR)Timer->Dpc;
|
|||
|
if ((Address > (Start - sizeof(KDPC))) &&
|
|||
|
(Address < End)) {
|
|||
|
KeBugCheckEx(TIMER_OR_DPC_INVALID,
|
|||
|
0x1,
|
|||
|
(ULONG_PTR)Address,
|
|||
|
(ULONG_PTR)Start,
|
|||
|
(ULONG_PTR)End);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Check the timer's DPC routine is not in the range.
|
|||
|
//
|
|||
|
|
|||
|
Address = (PUCHAR)Timer->Dpc->DeferredRoutine;
|
|||
|
if (Address >= Start && Address < End) {
|
|||
|
KeBugCheckEx(TIMER_OR_DPC_INVALID,
|
|||
|
0x2,
|
|||
|
(ULONG_PTR)Address,
|
|||
|
(ULONG_PTR)Start,
|
|||
|
(ULONG_PTR)End);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Index += 1;
|
|||
|
} while(Index < TIMER_TABLE_SIZE);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Unlock the dispatcher database and lower IRQL to its previous value
|
|||
|
//
|
|||
|
|
|||
|
KiUnlockDispatcherDatabase(OldIrql);
|
|||
|
}
|