mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 01:55:19 +00:00
43b181309e
Both due-times and interrupt time are unsigned, but were treated as signed in KiInsertTimerTable, which led to very long (e.g. INFINITE) waits being interpreted as having a negative due-time and being completed instantly. Mostly fixes kernel32_apitest QueueUserAPC
342 lines
8.9 KiB
C
342 lines
8.9 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: ntoskrnl/ke/timerobj.c
|
|
* PURPOSE: Handle Kernel Timers (Kernel-part of Executive Timers)
|
|
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* GLOBALS *******************************************************************/
|
|
|
|
KTIMER_TABLE_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE];
|
|
LARGE_INTEGER KiTimeIncrementReciprocal;
|
|
UCHAR KiTimeIncrementShiftCount;
|
|
BOOLEAN KiEnableTimerWatchdog = FALSE;
|
|
|
|
/* PRIVATE FUNCTIONS *********************************************************/
|
|
|
|
BOOLEAN
|
|
FASTCALL
|
|
KiInsertTreeTimer(IN PKTIMER Timer,
|
|
IN LARGE_INTEGER Interval)
|
|
{
|
|
BOOLEAN Inserted = FALSE;
|
|
ULONG Hand = 0;
|
|
PKSPIN_LOCK_QUEUE LockQueue;
|
|
DPRINT("KiInsertTreeTimer(): Timer %p, Interval: %I64d\n", Timer, Interval.QuadPart);
|
|
|
|
/* Setup the timer's due time */
|
|
if (KiComputeDueTime(Timer, Interval, &Hand))
|
|
{
|
|
/* Acquire the lock */
|
|
LockQueue = KiAcquireTimerLock(Hand);
|
|
|
|
/* Insert the timer */
|
|
if (KiInsertTimerTable(Timer, Hand))
|
|
{
|
|
/* It was already there, remove it */
|
|
KiRemoveEntryTimer(Timer);
|
|
Timer->Header.Inserted = FALSE;
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, we're now inserted */
|
|
Inserted = TRUE;
|
|
}
|
|
|
|
/* Release the lock */
|
|
KiReleaseTimerLock(LockQueue);
|
|
}
|
|
|
|
/* Release the lock and return insert status */
|
|
return Inserted;
|
|
}
|
|
|
|
BOOLEAN
|
|
FASTCALL
|
|
KiInsertTimerTable(IN PKTIMER Timer,
|
|
IN ULONG Hand)
|
|
{
|
|
ULONGLONG InterruptTime;
|
|
ULONGLONG DueTime = Timer->DueTime.QuadPart;
|
|
BOOLEAN Expired = FALSE;
|
|
PLIST_ENTRY ListHead, NextEntry;
|
|
PKTIMER CurrentTimer;
|
|
DPRINT("KiInsertTimerTable(): Timer %p, Hand: %lu\n", Timer, Hand);
|
|
|
|
/* Check if the period is zero */
|
|
if (!Timer->Period) Timer->Header.SignalState = FALSE;
|
|
|
|
/* Sanity check */
|
|
ASSERT(Hand == KiComputeTimerTableIndex(DueTime));
|
|
|
|
/* Loop the timer list backwards */
|
|
ListHead = &KiTimerTableListHead[Hand].Entry;
|
|
NextEntry = ListHead->Blink;
|
|
while (NextEntry != ListHead)
|
|
{
|
|
/* Get the timer */
|
|
CurrentTimer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
|
|
|
|
/* Now check if we can fit it before */
|
|
if ((ULONGLONG)DueTime >= CurrentTimer->DueTime.QuadPart) break;
|
|
|
|
/* Keep looping */
|
|
NextEntry = NextEntry->Blink;
|
|
}
|
|
|
|
/* Looped all the list, insert it here and get the interrupt time again */
|
|
InsertHeadList(NextEntry, &Timer->TimerListEntry);
|
|
|
|
/* Check if we didn't find it in the list */
|
|
if (NextEntry == ListHead)
|
|
{
|
|
/* Set the time */
|
|
KiTimerTableListHead[Hand].Time.QuadPart = DueTime;
|
|
|
|
/* Make sure it hasn't expired already */
|
|
InterruptTime = KeQueryInterruptTime();
|
|
if (DueTime <= InterruptTime) Expired = TRUE;
|
|
}
|
|
|
|
/* Return expired state */
|
|
return Expired;
|
|
}
|
|
|
|
BOOLEAN
|
|
FASTCALL
|
|
KiSignalTimer(IN PKTIMER Timer)
|
|
{
|
|
BOOLEAN RequestInterrupt = FALSE;
|
|
PKDPC Dpc = Timer->Dpc;
|
|
ULONG Period = Timer->Period;
|
|
LARGE_INTEGER Interval, SystemTime;
|
|
DPRINT("KiSignalTimer(): Timer %p\n", Timer);
|
|
|
|
/* Set default values */
|
|
Timer->Header.Inserted = FALSE;
|
|
Timer->Header.SignalState = TRUE;
|
|
|
|
/* Check if the timer has waiters */
|
|
if (!IsListEmpty(&Timer->Header.WaitListHead))
|
|
{
|
|
/* Check the type of event */
|
|
if (Timer->Header.Type == TimerNotificationObject)
|
|
{
|
|
/* Unwait the thread */
|
|
KxUnwaitThread(&Timer->Header, IO_NO_INCREMENT);
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise unwait the thread and signal the timer */
|
|
KxUnwaitThreadForEvent((PKEVENT)Timer, IO_NO_INCREMENT);
|
|
}
|
|
}
|
|
|
|
/* Check if we have a period */
|
|
if (Period)
|
|
{
|
|
/* Calculate the interval and insert the timer */
|
|
Interval.QuadPart = Int32x32To64(Period, -10000);
|
|
while (!KiInsertTreeTimer(Timer, Interval));
|
|
}
|
|
|
|
/* Check if we have a DPC */
|
|
if (Dpc)
|
|
{
|
|
/* Insert it in the queue */
|
|
KeQuerySystemTime(&SystemTime);
|
|
KeInsertQueueDpc(Dpc,
|
|
ULongToPtr(SystemTime.LowPart),
|
|
ULongToPtr(SystemTime.HighPart));
|
|
RequestInterrupt = TRUE;
|
|
}
|
|
|
|
/* Return whether we need to request a DPC interrupt or not */
|
|
return RequestInterrupt;
|
|
}
|
|
|
|
VOID
|
|
FASTCALL
|
|
KiCompleteTimer(IN PKTIMER Timer,
|
|
IN PKSPIN_LOCK_QUEUE LockQueue)
|
|
{
|
|
LIST_ENTRY ListHead;
|
|
BOOLEAN RequestInterrupt = FALSE;
|
|
DPRINT("KiCompleteTimer(): Timer %p, LockQueue: %p\n", Timer, LockQueue);
|
|
|
|
/* Remove it from the timer list */
|
|
KiRemoveEntryTimer(Timer);
|
|
|
|
/* Link the timer list to our stack */
|
|
ListHead.Flink = &Timer->TimerListEntry;
|
|
ListHead.Blink = &Timer->TimerListEntry;
|
|
Timer->TimerListEntry.Flink = &ListHead;
|
|
Timer->TimerListEntry.Blink = &ListHead;
|
|
|
|
/* Release the timer lock */
|
|
KiReleaseTimerLock(LockQueue);
|
|
|
|
/* Acquire dispatcher lock */
|
|
KiAcquireDispatcherLockAtSynchLevel();
|
|
|
|
/* Signal the timer if it's still on our list */
|
|
if (!IsListEmpty(&ListHead)) RequestInterrupt = KiSignalTimer(Timer);
|
|
|
|
/* Release the dispatcher lock */
|
|
KiReleaseDispatcherLockFromSynchLevel();
|
|
|
|
/* Request a DPC if needed */
|
|
if (RequestInterrupt) HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS **********************************************************/
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOLEAN
|
|
NTAPI
|
|
KeCancelTimer(IN OUT PKTIMER Timer)
|
|
{
|
|
KIRQL OldIrql;
|
|
BOOLEAN Inserted;
|
|
ASSERT_TIMER(Timer);
|
|
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
|
|
DPRINT("KeCancelTimer(): Timer %p\n", Timer);
|
|
|
|
/* Lock the Database and Raise IRQL */
|
|
OldIrql = KiAcquireDispatcherLock();
|
|
|
|
/* Check if it's inserted, and remove it if it is */
|
|
Inserted = Timer->Header.Inserted;
|
|
if (Inserted) KxRemoveTreeTimer(Timer);
|
|
|
|
/* Release Dispatcher Lock */
|
|
KiReleaseDispatcherLock(OldIrql);
|
|
|
|
/* Return the old state */
|
|
return Inserted;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
KeInitializeTimer(OUT PKTIMER Timer)
|
|
{
|
|
/* Call the New Function */
|
|
KeInitializeTimerEx(Timer, NotificationTimer);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
KeInitializeTimerEx(OUT PKTIMER Timer,
|
|
IN TIMER_TYPE Type)
|
|
{
|
|
DPRINT("KeInitializeTimerEx(): Timer %p, Type %s\n",
|
|
Timer, (Type == NotificationTimer) ?
|
|
"NotificationTimer" : "SynchronizationTimer");
|
|
|
|
/* Initialize the Dispatch Header */
|
|
Timer->Header.Type = TimerNotificationObject + Type;
|
|
//Timer->Header.TimerControlFlags = 0; // win does not init this field
|
|
Timer->Header.Hand = sizeof(KTIMER) / sizeof(ULONG);
|
|
Timer->Header.Inserted = 0; // win7: Timer->Header.TimerMiscFlags = 0;
|
|
Timer->Header.SignalState = 0;
|
|
InitializeListHead(&(Timer->Header.WaitListHead));
|
|
|
|
/* Initialize the Other data */
|
|
Timer->DueTime.QuadPart = 0;
|
|
Timer->Period = 0;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOLEAN
|
|
NTAPI
|
|
KeReadStateTimer(IN PKTIMER Timer)
|
|
{
|
|
/* Return the Signal State */
|
|
ASSERT_TIMER(Timer);
|
|
return (BOOLEAN)Timer->Header.SignalState;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOLEAN
|
|
NTAPI
|
|
KeSetTimer(IN OUT PKTIMER Timer,
|
|
IN LARGE_INTEGER DueTime,
|
|
IN PKDPC Dpc OPTIONAL)
|
|
{
|
|
/* Call the newer function and supply a period of 0 */
|
|
return KeSetTimerEx(Timer, DueTime, 0, Dpc);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOLEAN
|
|
NTAPI
|
|
KeSetTimerEx(IN OUT PKTIMER Timer,
|
|
IN LARGE_INTEGER DueTime,
|
|
IN LONG Period,
|
|
IN PKDPC Dpc OPTIONAL)
|
|
{
|
|
KIRQL OldIrql;
|
|
BOOLEAN Inserted;
|
|
ULONG Hand = 0;
|
|
BOOLEAN RequestInterrupt = FALSE;
|
|
ASSERT_TIMER(Timer);
|
|
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
|
|
DPRINT("KeSetTimerEx(): Timer %p, DueTime %I64d, Period %d, Dpc %p\n",
|
|
Timer, DueTime.QuadPart, Period, Dpc);
|
|
|
|
/* Lock the Database and Raise IRQL */
|
|
OldIrql = KiAcquireDispatcherLock();
|
|
|
|
/* Check if it's inserted, and remove it if it is */
|
|
Inserted = Timer->Header.Inserted;
|
|
if (Inserted) KxRemoveTreeTimer(Timer);
|
|
|
|
/* Set Default Timer Data */
|
|
Timer->Dpc = Dpc;
|
|
Timer->Period = Period;
|
|
if (!KiComputeDueTime(Timer, DueTime, &Hand))
|
|
{
|
|
/* Signal the timer */
|
|
RequestInterrupt = KiSignalTimer(Timer);
|
|
|
|
/* Release the dispatcher lock */
|
|
KiReleaseDispatcherLockFromSynchLevel();
|
|
|
|
/* Check if we need to do an interrupt */
|
|
if (RequestInterrupt) HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
|
|
}
|
|
else
|
|
{
|
|
/* Insert the timer */
|
|
Timer->Header.SignalState = FALSE;
|
|
KxInsertTimer(Timer, Hand);
|
|
}
|
|
|
|
/* Exit the dispatcher */
|
|
KiExitDispatcher(OldIrql);
|
|
|
|
/* Return old state */
|
|
return Inserted;
|
|
}
|
|
|