Files
nt-lite/code/kernel/ke_timer.c
T

220 lines
6.3 KiB
C

// Kernel waitable timer objects. This file owns timer deadlines and timer wait signaling.
#include <stdint.h>
#include <string.h>
#include <kernel/ke_timer.h>
#include <kernel/ke_sched.h>
#include <kernel/ob_object.h>
#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);
}
}