// Kernel waitable timer objects. This file owns timer deadlines and timer wait signaling. #include #include #include #include #include #define TIMER_SIGNAL_BATCH 32u static LIST_ENTRY gTimerList; static SPINLOCK gTimerLock; static uint8_t gTimerInitialized; // Initializes the global timer list on first use. static void KiTimerEnsureInitialized(void) { if (gTimerInitialized) return; gTimerLock = (SPINLOCK)SPINLOCK_INIT; LIST_INIT(&gTimerList); gTimerInitialized = 1; } // Reads the scheduler tick value used by timer deadlines. static uint64_t KiTimerReadSchedulerTick(void) { KE_SCHED_STATS stats; KeGetSchedulerStats(&stats); return stats.Ticks; } // Converts a millisecond interval to at least one scheduler tick. static uint64_t KiTimerMillisecondsToTicks(uint64_t milliseconds) { if (milliseconds == 0) return 0; uint64_t ticks = milliseconds / SCHED_TICK_MS; if ((milliseconds % SCHED_TICK_MS) != 0) ticks++; return ticks ? ticks : 1; } // Adds a tick interval to the current scheduler tick with saturation. static uint64_t KiTimerDeadlineFromNow(uint64_t intervalTicks) { uint64_t now = KiTimerReadSchedulerTick(); uint64_t due = now + intervalTicks; return due < now ? UINT64_MAX : due; } // Removes an active timer from the global deadline list while the timer lock is held. static uint8_t KiTimerRemoveLocked(KTIMER* timer) { if (!timer || !timer->Linked) return 0; ListRemove(&timer->Link); timer->Linked = 0; timer->Active = 0; timer->DueTick = 0; timer->PeriodTicks = 0; return 1; } // Implements the timer Destroy operation. static void KiTimerDestroy(OBJECT* obj) { KTIMER* timer = (KTIMER*)obj; if (!timer) return; KiTimerEnsureInitialized(); uintptr_t flags = KeAcquireSpinlock(&gTimerLock); (void)KiTimerRemoveLocked(timer); KeReleaseSpinlock(&gTimerLock, flags); if (timer->Event) { ObDereferenceObject(&timer->Event->Header); timer->Event = (EVENT*)0; } } // Creates an unset waitable timer object. KSTATUS KeTimerCreate(KTIMER** out) { if (!out) return STATUS_INVALID_PARAMETER; *out = (KTIMER*)0; KiTimerEnsureInitialized(); KTIMER* timer = (KTIMER*)ObAllocateObject(ObjectTypeTimer, sizeof(KTIMER)); if (!timer) return STATUS_NO_MEMORY; timer->Header.Destroy = KiTimerDestroy; LIST_INIT(&timer->Link); KSTATUS s = KeEventCreate(EventAutoReset, 0, &timer->Event); if (!KSUCCESS(s)) { ObDereferenceObject(&timer->Header); return s; } *out = timer; return STATUS_SUCCESS; } // Arms or rearms a timer with a relative due time and optional period. KSTATUS KeTimerSet(KTIMER* timer, uint64_t dueTimeMilliseconds, uint64_t periodMilliseconds) { if (!timer || !timer->Event) return STATUS_INVALID_PARAMETER; KiTimerEnsureInitialized(); uint64_t dueTicks = KiTimerMillisecondsToTicks(dueTimeMilliseconds); uint64_t periodTicks = KiTimerMillisecondsToTicks(periodMilliseconds); uint8_t dropListReference = 0; uint8_t addListReference = 0; uint8_t signalNow = dueTicks == 0; EVENT* event = timer->Event; ObReferenceObject(&event->Header); uintptr_t flags = KeAcquireSpinlock(&gTimerLock); if (timer->Linked) { if (signalNow && periodTicks == 0) { dropListReference = KiTimerRemoveLocked(timer); } else { timer->DueTick = KiTimerDeadlineFromNow(signalNow ? periodTicks : dueTicks); timer->PeriodTicks = periodTicks; timer->Active = 1; } } else if (!signalNow || periodTicks != 0) { timer->DueTick = KiTimerDeadlineFromNow(signalNow ? periodTicks : dueTicks); timer->PeriodTicks = periodTicks; timer->Active = 1; timer->Linked = 1; ListInsertTail(&gTimerList, &timer->Link); addListReference = 1; } KeReleaseSpinlock(&gTimerLock, flags); if (addListReference) ObReferenceObject(&timer->Header); if (dropListReference) ObDereferenceObject(&timer->Header); KeClearEvent(event); if (signalNow) KeSignalEvent(event); ObDereferenceObject(&event->Header); return STATUS_SUCCESS; } // Cancels a timer and clears any retained signal. KSTATUS KeTimerCancel(KTIMER* timer) { if (!timer || !timer->Event) return STATUS_INVALID_PARAMETER; KiTimerEnsureInitialized(); uintptr_t flags = KeAcquireSpinlock(&gTimerLock); uint8_t hadListReference = KiTimerRemoveLocked(timer); KeReleaseSpinlock(&gTimerLock, flags); if (hadListReference) ObDereferenceObject(&timer->Header); KeClearEvent(timer->Event); return STATUS_SUCCESS; } // Returns the event that backs timer waits. EVENT* KeTimerWaitEvent(KTIMER* timer) { if (!timer) return (EVENT*)0; return timer->Event; } // Services expired timers from the scheduler tick path. void KeTimerTick(void) { KiTimerEnsureInitialized(); EVENT* events[TIMER_SIGNAL_BATCH]; KTIMER* listReferencesToDrop[TIMER_SIGNAL_BATCH]; uint32_t count = 0; uint32_t dropCount = 0; uint64_t now = KiTimerReadSchedulerTick(); uintptr_t flags = KeAcquireSpinlock(&gTimerLock); LIST_ENTRY* node = gTimerList.Fwd; while (node != &gTimerList && count < TIMER_SIGNAL_BATCH) { LIST_ENTRY* next = node->Fwd; KTIMER* timer = CONTAINING_RECORD(node, KTIMER, Link); if (timer->Active && timer->DueTick <= now) { if (timer->Event) { ObReferenceObject(&timer->Event->Header); events[count++] = timer->Event; } if (timer->PeriodTicks != 0) { uint64_t due = now + timer->PeriodTicks; timer->DueTick = due < now ? UINT64_MAX : due; } else { if (KiTimerRemoveLocked(timer)) listReferencesToDrop[dropCount++] = timer; } } node = next; } KeReleaseSpinlock(&gTimerLock, flags); for (uint32_t i = 0; i < dropCount; i++) ObDereferenceObject(&listReferencesToDrop[i]->Header); for (uint32_t i = 0; i < count; i++) { KeSignalEvent(events[i]); ObDereferenceObject(&events[i]->Header); } }