1 /* 2 * PROJECT: ReactOS Kernel 3 * LICENSE: GPL - See COPYING in the top level directory 4 * FILE: ntoskrnl/ke/timerobj.c 5 * PURPOSE: Handle Kernel Timers (Kernel-part of Executive Timers) 6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org) 7 */ 8 9 /* INCLUDES ******************************************************************/ 10 11 #include <ntoskrnl.h> 12 #define NDEBUG 13 #include <debug.h> 14 15 /* GLOBALS *******************************************************************/ 16 17 KTIMER_TABLE_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE]; 18 LARGE_INTEGER KiTimeIncrementReciprocal; 19 UCHAR KiTimeIncrementShiftCount; 20 BOOLEAN KiEnableTimerWatchdog = FALSE; 21 22 /* PRIVATE FUNCTIONS *********************************************************/ 23 24 BOOLEAN 25 FASTCALL 26 KiInsertTreeTimer(IN PKTIMER Timer, 27 IN LARGE_INTEGER Interval) 28 { 29 BOOLEAN Inserted = FALSE; 30 ULONG Hand = 0; 31 PKSPIN_LOCK_QUEUE LockQueue; 32 DPRINT("KiInsertTreeTimer(): Timer %p, Interval: %I64d\n", Timer, Interval.QuadPart); 33 34 /* Setup the timer's due time */ 35 if (KiComputeDueTime(Timer, Interval, &Hand)) 36 { 37 /* Acquire the lock */ 38 LockQueue = KiAcquireTimerLock(Hand); 39 40 /* Insert the timer */ 41 if (KiInsertTimerTable(Timer, Hand)) 42 { 43 /* It was already there, remove it */ 44 KiRemoveEntryTimer(Timer); 45 Timer->Header.Inserted = FALSE; 46 } 47 else 48 { 49 /* Otherwise, we're now inserted */ 50 Inserted = TRUE; 51 } 52 53 /* Release the lock */ 54 KiReleaseTimerLock(LockQueue); 55 } 56 57 /* Release the lock and return insert status */ 58 return Inserted; 59 } 60 61 BOOLEAN 62 FASTCALL 63 KiInsertTimerTable(IN PKTIMER Timer, 64 IN ULONG Hand) 65 { 66 LARGE_INTEGER InterruptTime; 67 LONGLONG DueTime = Timer->DueTime.QuadPart; 68 BOOLEAN Expired = FALSE; 69 PLIST_ENTRY ListHead, NextEntry; 70 PKTIMER CurrentTimer; 71 DPRINT("KiInsertTimerTable(): Timer %p, Hand: %lu\n", Timer, Hand); 72 73 /* Check if the period is zero */ 74 if (!Timer->Period) Timer->Header.SignalState = FALSE; 75 76 /* Sanity check */ 77 ASSERT(Hand == KiComputeTimerTableIndex(DueTime)); 78 79 /* Loop the timer list backwards */ 80 ListHead = &KiTimerTableListHead[Hand].Entry; 81 NextEntry = ListHead->Blink; 82 while (NextEntry != ListHead) 83 { 84 /* Get the timer */ 85 CurrentTimer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry); 86 87 /* Now check if we can fit it before */ 88 if ((ULONGLONG)DueTime >= CurrentTimer->DueTime.QuadPart) break; 89 90 /* Keep looping */ 91 NextEntry = NextEntry->Blink; 92 } 93 94 /* Looped all the list, insert it here and get the interrupt time again */ 95 InsertHeadList(NextEntry, &Timer->TimerListEntry); 96 97 /* Check if we didn't find it in the list */ 98 if (NextEntry == ListHead) 99 { 100 /* Set the time */ 101 KiTimerTableListHead[Hand].Time.QuadPart = DueTime; 102 103 /* Make sure it hasn't expired already */ 104 InterruptTime.QuadPart = KeQueryInterruptTime(); 105 if (DueTime <= InterruptTime.QuadPart) Expired = TRUE; 106 } 107 108 /* Return expired state */ 109 return Expired; 110 } 111 112 BOOLEAN 113 FASTCALL 114 KiSignalTimer(IN PKTIMER Timer) 115 { 116 BOOLEAN RequestInterrupt = FALSE; 117 PKDPC Dpc = Timer->Dpc; 118 ULONG Period = Timer->Period; 119 LARGE_INTEGER Interval, SystemTime; 120 DPRINT("KiSignalTimer(): Timer %p\n", Timer); 121 122 /* Set default values */ 123 Timer->Header.Inserted = FALSE; 124 Timer->Header.SignalState = TRUE; 125 126 /* Check if the timer has waiters */ 127 if (!IsListEmpty(&Timer->Header.WaitListHead)) 128 { 129 /* Check the type of event */ 130 if (Timer->Header.Type == TimerNotificationObject) 131 { 132 /* Unwait the thread */ 133 KxUnwaitThread(&Timer->Header, IO_NO_INCREMENT); 134 } 135 else 136 { 137 /* Otherwise unwait the thread and signal the timer */ 138 KxUnwaitThreadForEvent((PKEVENT)Timer, IO_NO_INCREMENT); 139 } 140 } 141 142 /* Check if we have a period */ 143 if (Period) 144 { 145 /* Calculate the interval and insert the timer */ 146 Interval.QuadPart = Int32x32To64(Period, -10000); 147 while (!KiInsertTreeTimer(Timer, Interval)); 148 } 149 150 /* Check if we have a DPC */ 151 if (Dpc) 152 { 153 /* Insert it in the queue */ 154 KeQuerySystemTime(&SystemTime); 155 KeInsertQueueDpc(Dpc, 156 ULongToPtr(SystemTime.LowPart), 157 ULongToPtr(SystemTime.HighPart)); 158 RequestInterrupt = TRUE; 159 } 160 161 /* Return whether we need to request a DPC interrupt or not */ 162 return RequestInterrupt; 163 } 164 165 VOID 166 FASTCALL 167 KiCompleteTimer(IN PKTIMER Timer, 168 IN PKSPIN_LOCK_QUEUE LockQueue) 169 { 170 LIST_ENTRY ListHead; 171 BOOLEAN RequestInterrupt = FALSE; 172 DPRINT("KiCompleteTimer(): Timer %p, LockQueue: %p\n", Timer, LockQueue); 173 174 /* Remove it from the timer list */ 175 KiRemoveEntryTimer(Timer); 176 177 /* Link the timer list to our stack */ 178 ListHead.Flink = &Timer->TimerListEntry; 179 ListHead.Blink = &Timer->TimerListEntry; 180 Timer->TimerListEntry.Flink = &ListHead; 181 Timer->TimerListEntry.Blink = &ListHead; 182 183 /* Release the timer lock */ 184 KiReleaseTimerLock(LockQueue); 185 186 /* Acquire dispatcher lock */ 187 KiAcquireDispatcherLockAtDpcLevel(); 188 189 /* Signal the timer if it's still on our list */ 190 if (!IsListEmpty(&ListHead)) RequestInterrupt = KiSignalTimer(Timer); 191 192 /* Release the dispatcher lock */ 193 KiReleaseDispatcherLockFromDpcLevel(); 194 195 /* Request a DPC if needed */ 196 if (RequestInterrupt) HalRequestSoftwareInterrupt(DISPATCH_LEVEL); 197 } 198 199 /* PUBLIC FUNCTIONS **********************************************************/ 200 201 /* 202 * @implemented 203 */ 204 BOOLEAN 205 NTAPI 206 KeCancelTimer(IN OUT PKTIMER Timer) 207 { 208 KIRQL OldIrql; 209 BOOLEAN Inserted; 210 ASSERT_TIMER(Timer); 211 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); 212 DPRINT("KeCancelTimer(): Timer %p\n", Timer); 213 214 /* Lock the Database and Raise IRQL */ 215 OldIrql = KiAcquireDispatcherLock(); 216 217 /* Check if it's inserted, and remove it if it is */ 218 Inserted = Timer->Header.Inserted; 219 if (Inserted) KxRemoveTreeTimer(Timer); 220 221 /* Release Dispatcher Lock */ 222 KiReleaseDispatcherLock(OldIrql); 223 224 /* Return the old state */ 225 return Inserted; 226 } 227 228 /* 229 * @implemented 230 */ 231 VOID 232 NTAPI 233 KeInitializeTimer(OUT PKTIMER Timer) 234 { 235 /* Call the New Function */ 236 KeInitializeTimerEx(Timer, NotificationTimer); 237 } 238 239 /* 240 * @implemented 241 */ 242 VOID 243 NTAPI 244 KeInitializeTimerEx(OUT PKTIMER Timer, 245 IN TIMER_TYPE Type) 246 { 247 DPRINT("KeInitializeTimerEx(): Timer %p, Type %s\n", 248 Timer, (Type == NotificationTimer) ? 249 "NotificationTimer" : "SynchronizationTimer"); 250 251 /* Initialize the Dispatch Header */ 252 Timer->Header.Type = TimerNotificationObject + Type; 253 //Timer->Header.TimerControlFlags = 0; // win does not init this field 254 Timer->Header.Hand = sizeof(KTIMER) / sizeof(ULONG); 255 Timer->Header.Inserted = 0; // win7: Timer->Header.TimerMiscFlags = 0; 256 Timer->Header.SignalState = 0; 257 InitializeListHead(&(Timer->Header.WaitListHead)); 258 259 /* Initialize the Other data */ 260 Timer->DueTime.QuadPart = 0; 261 Timer->Period = 0; 262 } 263 264 /* 265 * @implemented 266 */ 267 BOOLEAN 268 NTAPI 269 KeReadStateTimer(IN PKTIMER Timer) 270 { 271 /* Return the Signal State */ 272 ASSERT_TIMER(Timer); 273 return (BOOLEAN)Timer->Header.SignalState; 274 } 275 276 /* 277 * @implemented 278 */ 279 BOOLEAN 280 NTAPI 281 KeSetTimer(IN OUT PKTIMER Timer, 282 IN LARGE_INTEGER DueTime, 283 IN PKDPC Dpc OPTIONAL) 284 { 285 /* Call the newer function and supply a period of 0 */ 286 return KeSetTimerEx(Timer, DueTime, 0, Dpc); 287 } 288 289 /* 290 * @implemented 291 */ 292 BOOLEAN 293 NTAPI 294 KeSetTimerEx(IN OUT PKTIMER Timer, 295 IN LARGE_INTEGER DueTime, 296 IN LONG Period, 297 IN PKDPC Dpc OPTIONAL) 298 { 299 KIRQL OldIrql; 300 BOOLEAN Inserted; 301 ULONG Hand = 0; 302 BOOLEAN RequestInterrupt = FALSE; 303 ASSERT_TIMER(Timer); 304 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); 305 DPRINT("KeSetTimerEx(): Timer %p, DueTime %I64d, Period %d, Dpc %p\n", 306 Timer, DueTime.QuadPart, Period, Dpc); 307 308 /* Lock the Database and Raise IRQL */ 309 OldIrql = KiAcquireDispatcherLock(); 310 311 /* Check if it's inserted, and remove it if it is */ 312 Inserted = Timer->Header.Inserted; 313 if (Inserted) KxRemoveTreeTimer(Timer); 314 315 /* Set Default Timer Data */ 316 Timer->Dpc = Dpc; 317 Timer->Period = Period; 318 if (!KiComputeDueTime(Timer, DueTime, &Hand)) 319 { 320 /* Signal the timer */ 321 RequestInterrupt = KiSignalTimer(Timer); 322 323 /* Release the dispatcher lock */ 324 KiReleaseDispatcherLockFromDpcLevel(); 325 326 /* Check if we need to do an interrupt */ 327 if (RequestInterrupt) HalRequestSoftwareInterrupt(DISPATCH_LEVEL); 328 } 329 else 330 { 331 /* Insert the timer */ 332 Timer->Header.SignalState = FALSE; 333 KxInsertTimer(Timer, Hand); 334 } 335 336 /* Exit the dispatcher */ 337 KiExitDispatcher(OldIrql); 338 339 /* Return old state */ 340 return Inserted; 341 } 342 343