/* $Id$ * * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS kernel * FILE: ntoskrnl/ke/timer.c * PURPOSE: Handle timers * PROGRAMMER: David Welch (welch@mcmail.com) * UPDATE HISTORY: * 28/05/98: Created * 12/3/99: Phillip Susi: enabled the timers, fixed spin lock */ /* NOTES ******************************************************************/ /* * System time units are 100-nanosecond intervals */ /* INCLUDES ***************************************************************/ #include #include #include #include #include #define NDEBUG #include /* TYPES *****************************************************************/ #define TIMER_IRQ 0 /* GLOBALS ****************************************************************/ #ifndef LIBCAPTIVE /* * Current time */ static LARGE_INTEGER boot_time = (LARGE_INTEGER)0LL; static LARGE_INTEGER system_time = (LARGE_INTEGER)0LL; /* * Number of timer interrupts since initialisation */ volatile ULONGLONG KeTickCount = 0; volatile ULONG KiRawTicks = 0; /* * The increment in the system clock every timer tick (in system time units) * * = (1/18.2)*10^9 * * RJJ was 54945055 */ #define CLOCK_INCREMENT (100000) /* * PURPOSE: List of timers */ static LIST_ENTRY TimerListHead; #endif /* LIBCAPTIVE */ static KSPIN_LOCK TimerListLock; #ifndef LIBCAPTIVE static KSPIN_LOCK TimerValueLock; static KDPC ExpireTimerDpc; /* must raise IRQL to PROFILE_LEVEL and grab spin lock there, to sync with ISR */ extern ULONG PiNrRunnableThreads; #define MICROSECONDS_PER_TICK (10000) #define TICKS_TO_CALIBRATE (1) #define CALIBRATE_PERIOD (MICROSECONDS_PER_TICK * TICKS_TO_CALIBRATE) #define SYSTEM_TIME_UNITS_PER_MSEC (10000) static BOOLEAN TimerInitDone = FALSE; #endif /* LIBCAPTIVE */ /* FUNCTIONS **************************************************************/ #ifndef LIBCAPTIVE NTSTATUS STDCALL NtQueryTimerResolution(OUT PULONG MinimumResolution, OUT PULONG MaximumResolution, OUT PULONG ActualResolution) { UNIMPLEMENTED; } NTSTATUS STDCALL NtSetTimerResolution(IN ULONG RequestedResolution, IN BOOL SetOrUnset, OUT PULONG ActualResolution) { UNIMPLEMENTED; } NTSTATUS STDCALL NtQueryPerformanceCounter(IN PLARGE_INTEGER Counter, IN PLARGE_INTEGER Frequency) { LARGE_INTEGER PerfCounter; LARGE_INTEGER PerfFrequency; PerfCounter = KeQueryPerformanceCounter(&PerfFrequency); if (Counter != NULL) Counter->QuadPart = PerfCounter.QuadPart; if (Frequency != NULL) Frequency->QuadPart = PerfFrequency.QuadPart; return(STATUS_SUCCESS); } NTSTATUS STDCALL NtDelayExecution(IN ULONG Alertable, IN TIME* Interval) { NTSTATUS Status; LARGE_INTEGER Timeout; Timeout = *((PLARGE_INTEGER)Interval); DPRINT("NtDelayExecution(Alertable %d, Internal %x) IntervalP %x\n", Alertable, Internal, Timeout); DPRINT("Execution delay is %d/%d\n", Timeout.u.HighPart, Timeout.u.LowPart); Status = KeDelayExecutionThread(UserMode, Alertable, &Timeout); return(Status); } NTSTATUS STDCALL KeDelayExecutionThread (KPROCESSOR_MODE WaitMode, BOOLEAN Alertable, PLARGE_INTEGER Interval) /* * FUNCTION: Puts the current thread into an alertable or nonalertable * wait state for a given internal * ARGUMENTS: * WaitMode = Processor mode in which the caller is waiting * Altertable = Specifies if the wait is alertable * Interval = Specifies the interval to wait * RETURNS: Status */ { PKTHREAD Thread = KeGetCurrentThread(); KeInitializeTimer(&Thread->Timer); KeSetTimer(&Thread->Timer, *Interval, NULL); return (KeWaitForSingleObject(&Thread->Timer, Executive, UserMode, Alertable, NULL)); } ULONG STDCALL KeQueryTimeIncrement(VOID) /* * FUNCTION: Gets the increment (in 100-nanosecond units) that is added to * the system clock every time the clock interrupts * RETURNS: The increment */ { return(CLOCK_INCREMENT); } VOID STDCALL KeQuerySystemTime(PLARGE_INTEGER CurrentTime) /* * FUNCTION: Gets the current system time * ARGUMENTS: * CurrentTime (OUT) = The routine stores the current time here * NOTE: The time is the number of 100-nanosecond intervals since the * 1st of January, 1601. */ { KIRQL oldIrql; KeRaiseIrql(PROFILE_LEVEL, &oldIrql); KeAcquireSpinLockAtDpcLevel(&TimerValueLock); *CurrentTime = system_time; KeReleaseSpinLock(&TimerValueLock, oldIrql); } NTSTATUS STDCALL NtGetTickCount (PULONG UpTime) { LARGE_INTEGER TickCount; if (UpTime == NULL) return(STATUS_INVALID_PARAMETER); KeQueryTickCount(&TickCount); *UpTime = TickCount.u.LowPart; return (STATUS_SUCCESS); } BOOLEAN STDCALL KeSetTimer (PKTIMER Timer, LARGE_INTEGER DueTime, PKDPC Dpc) /* * FUNCTION: Sets the absolute or relative interval at which a timer object * is to be set to the signaled state and optionally supplies a * CustomTimerDpc to be executed when the timer expires. * ARGUMENTS: * Timer = Points to a previously initialized timer object * DueTimer = If positive then absolute time to expire at * If negative then the relative time to expire at * Dpc = If non-NULL then a dpc to be called when the timer expires * RETURNS: True if the timer was already in the system timer queue * False otherwise */ { return(KeSetTimerEx(Timer, DueTime, 0, Dpc)); } BOOLEAN STDCALL KeSetTimerEx (PKTIMER Timer, LARGE_INTEGER DueTime, LONG Period, PKDPC Dpc) /* * FUNCTION: Sets the absolute or relative interval at which a timer object * is to be set to the signaled state and optionally supplies a * CustomTimerDpc to be executed when the timer expires. * ARGUMENTS: * Timer = Points to a previously initialized timer object * DueTimer = If positive then absolute time to expire at * If negative then the relative time to expire at * Dpc = If non-NULL then a dpc to be called when the timer expires * RETURNS: True if the timer was already in the system timer queue * False otherwise */ { KIRQL oldlvl; LARGE_INTEGER SystemTime; DPRINT("KeSetTimerEx(Timer %x), DueTime: \n",Timer); KeRaiseIrql(PROFILE_LEVEL, &oldlvl); KeAcquireSpinLockAtDpcLevel(&TimerValueLock); SystemTime = system_time; KeReleaseSpinLock(&TimerValueLock, DISPATCH_LEVEL); KeAcquireSpinLockAtDpcLevel(&TimerListLock); Timer->Dpc = Dpc; if (DueTime.QuadPart < 0) { Timer->DueTime.QuadPart = SystemTime.QuadPart - DueTime.QuadPart; } else { Timer->DueTime.QuadPart = DueTime.QuadPart; } Timer->Period = Period; Timer->Header.SignalState = FALSE; if (Timer->TimerListEntry.Flink != NULL) { KeReleaseSpinLock(&TimerListLock, oldlvl); return(TRUE); } InsertTailList(&TimerListHead,&Timer->TimerListEntry); KeReleaseSpinLock(&TimerListLock, oldlvl); return(FALSE); } #endif /* LIBCAPTIVE */ BOOLEAN STDCALL KeCancelTimer (PKTIMER Timer) /* * FUNCTION: Removes a timer from the system timer list * ARGUMENTS: * Timer = timer to cancel * RETURNS: True if the timer was running * False otherwise */ { KIRQL oldlvl; DPRINT("KeCancelTimer(Timer %x)\n",Timer); KeAcquireSpinLock(&TimerListLock, &oldlvl); if (Timer->TimerListEntry.Flink == NULL) { KeReleaseSpinLock(&TimerListLock, oldlvl); return(FALSE); } RemoveEntryList(&Timer->TimerListEntry); Timer->TimerListEntry.Flink = Timer->TimerListEntry.Blink = NULL; KeReleaseSpinLock(&TimerListLock, oldlvl); return(TRUE); } #ifndef LIBCAPTIVE BOOLEAN STDCALL KeReadStateTimer (PKTIMER Timer) { return(Timer->Header.SignalState); } #endif /* LIBCAPTIVE */ VOID STDCALL KeInitializeTimer (PKTIMER Timer) /* * FUNCTION: Initalizes a kernel timer object * ARGUMENTS: * Timer = caller supplied storage for the timer * NOTE: This function initializes a notification timer */ { KeInitializeTimerEx(Timer, NotificationTimer); } VOID STDCALL KeInitializeTimerEx (PKTIMER Timer, TIMER_TYPE Type) /* * FUNCTION: Initializes a kernel timer object * ARGUMENTS: * Timer = caller supplied storage for the timer * Type = the type of timer (notification or synchronization) * NOTE: When a notification type expires all waiting threads are released * and the timer remains signalled until it is explicitly reset. When a * syncrhonization timer expires its state is set to signalled until a * single waiting thread is released and then the timer is reset. */ { ULONG IType; if (Type == NotificationTimer) { IType = InternalNotificationTimer; } else if (Type == SynchronizationTimer) { IType = InternalSynchronizationTimer; } else { assert(FALSE); return; } KeInitializeDispatcherHeader(&Timer->Header, IType, sizeof(KTIMER) / sizeof(ULONG), FALSE); Timer->TimerListEntry.Flink = Timer->TimerListEntry.Blink = NULL; } #ifndef LIBCAPTIVE VOID STDCALL KeQueryTickCount(PLARGE_INTEGER TickCount) /* * FUNCTION: Returns the number of ticks since the system was booted * ARGUMENTS: * TickCount (OUT) = Points to storage for the number of ticks */ { TickCount->QuadPart = KeTickCount; } STATIC VOID HandleExpiredTimer(PKTIMER current) { DPRINT("HandleExpiredTime(current %x)\n",current); if (current->Dpc != NULL) { DPRINT("current->Dpc %x current->Dpc->DeferredRoutine %x\n", current->Dpc, current->Dpc->DeferredRoutine); KeInsertQueueDpc(current->Dpc, NULL, NULL); DPRINT("Finished dpc routine\n"); } KeAcquireDispatcherDatabaseLock(FALSE); current->Header.SignalState = TRUE; KeDispatcherObjectWake(¤t->Header); KeReleaseDispatcherDatabaseLock(FALSE); if (current->Period != 0) { current->DueTime.QuadPart += current->Period * SYSTEM_TIME_UNITS_PER_MSEC; } else { RemoveEntryList(¤t->TimerListEntry); current->TimerListEntry.Flink = current->TimerListEntry.Blink = NULL; } } VOID STDCALL KeExpireTimers(PKDPC Dpc, PVOID Context1, PVOID Arg1, PVOID Arg2) { PLIST_ENTRY current_entry = NULL; PKTIMER current = NULL; ULONG Eip = (ULONG)Arg1; KIRQL oldIrql; LARGE_INTEGER SystemTime; DPRINT("KeExpireTimers()\n"); KeRaiseIrql(PROFILE_LEVEL, &oldIrql); KeAcquireSpinLockAtDpcLevel(&TimerValueLock); SystemTime = system_time; KeReleaseSpinLock(&TimerValueLock, oldIrql); KeAcquireSpinLockAtDpcLevel(&TimerListLock); if (KeGetCurrentIrql() > DISPATCH_LEVEL) { DPRINT1("-----------------------------\n"); KeBugCheck(0); } current_entry = TimerListHead.Flink; while (current_entry != &TimerListHead) { current = CONTAINING_RECORD(current_entry, KTIMER, TimerListEntry); current_entry = current_entry->Flink; if ((ULONGLONG) SystemTime.QuadPart >= current->DueTime.QuadPart) { HandleExpiredTimer(current); } } KiAddProfileEvent(ProfileTime, Eip); KeReleaseSpinLockFromDpcLevel(&TimerListLock); } VOID KiUpdateSystemTime(KIRQL oldIrql, ULONG Eip) /* * FUNCTION: Handles a timer interrupt */ { assert(KeGetCurrentIrql() == PROFILE_LEVEL); KiRawTicks++; if (TimerInitDone == FALSE) { return; } /* * Increment the number of timers ticks */ KeTickCount++; SharedUserData->TickCountLow++; KeAcquireSpinLockAtDpcLevel(&TimerValueLock); system_time.QuadPart += CLOCK_INCREMENT; KeReleaseSpinLockFromDpcLevel(&TimerValueLock); /* * Queue a DPC that will expire timers */ KeInsertQueueDpc(&ExpireTimerDpc, (PVOID)Eip, 0); } VOID KeInitializeTimerImpl(VOID) /* * FUNCTION: Initializes timer irq handling * NOTE: This is only called once from main() */ { TIME_FIELDS TimeFields; LARGE_INTEGER SystemBootTime; DPRINT("KeInitializeTimerImpl()\n"); InitializeListHead(&TimerListHead); KeInitializeSpinLock(&TimerListLock); KeInitializeSpinLock(&TimerValueLock); KeInitializeDpc(&ExpireTimerDpc, KeExpireTimers, 0); TimerInitDone = TRUE; /* * Calculate the starting time for the system clock */ HalQueryRealTimeClock(&TimeFields); RtlTimeFieldsToTime(&TimeFields, &SystemBootTime); boot_time=SystemBootTime; system_time=boot_time; DPRINT("Finished KeInitializeTimerImpl()\n"); } #endif /* LIBCAPTIVE */