118 lines
3.6 KiB
C
118 lines
3.6 KiB
C
// Kernel passive-level work queue. This file owns the generic mechanism used
|
|
// to move ISR/DPC-originated work to a sleepable system thread context.
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <intrin.h>
|
|
|
|
#include <kernel/ke_work.h>
|
|
#include <kernel/ke_event.h>
|
|
#include <kernel/ke_spinlock.h>
|
|
#include <kernel/ke_thread.h>
|
|
#include <kernel/ke_sched.h>
|
|
#include <kernel/ke_irql.h>
|
|
#include <kernel/ke_debug.h>
|
|
|
|
static SPINLOCK gWorkQueueLock = SPINLOCK_INIT;
|
|
static LIST_ENTRY gWorkQueueList;
|
|
static EVENT* gWorkQueueEvent;
|
|
static volatile long gWorkQueueListReady;
|
|
static volatile long gWorkQueueStartState; // 0=not started, 1=starting, 2=started.
|
|
|
|
static void KiWorkQueueWorker(void* arg) {
|
|
(void)arg;
|
|
KeAssertIrql(PASSIVE_LEVEL);
|
|
|
|
for (;;) {
|
|
(void)KeWaitForEvent(gWorkQueueEvent, 0);
|
|
|
|
for (;;) {
|
|
KWORK_ITEM* item = (KWORK_ITEM*)0;
|
|
uintptr_t flags = KeAcquireSpinlock(&gWorkQueueLock);
|
|
if (!LIST_IS_EMPTY(&gWorkQueueList)) {
|
|
LIST_ENTRY* node = ListPopHead(&gWorkQueueList);
|
|
item = CONTAINING_RECORD(node, KWORK_ITEM, Link);
|
|
}
|
|
KeReleaseSpinlock(&gWorkQueueLock, flags);
|
|
|
|
if (!item)
|
|
break;
|
|
|
|
// Allow the item to be queued again while it is running. This keeps
|
|
// level-triggered users such as cursor updates naturally coalesced
|
|
// but does not lose an update that arrives during the callback.
|
|
_InterlockedExchange(&item->Queued, 0);
|
|
if (item->Routine)
|
|
item->Routine(item->Context);
|
|
}
|
|
}
|
|
}
|
|
|
|
void KeInitializeWorkItem(KWORK_ITEM* item, KWORK_ROUTINE routine, void* context) {
|
|
if (!item)
|
|
return;
|
|
LIST_INIT(&item->Link);
|
|
item->Routine = routine;
|
|
item->Context = context;
|
|
item->Queued = 0;
|
|
}
|
|
|
|
bool KeIsWorkQueueStarted(void) {
|
|
return _InterlockedCompareExchange(&gWorkQueueStartState, 2, 2) == 2;
|
|
}
|
|
|
|
bool KeQueueWorkItem(KWORK_ITEM* item) {
|
|
if (!item || !item->Routine)
|
|
return false;
|
|
|
|
if (_InterlockedCompareExchange(&item->Queued, 1, 0) != 0)
|
|
return false;
|
|
|
|
if (!_InterlockedCompareExchange(&gWorkQueueListReady, 1, 1)) {
|
|
_InterlockedExchange(&item->Queued, 0);
|
|
return false;
|
|
}
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&gWorkQueueLock);
|
|
ListInsertTail(&gWorkQueueList, &item->Link);
|
|
EVENT* event = gWorkQueueEvent;
|
|
KeReleaseSpinlock(&gWorkQueueLock, flags);
|
|
|
|
if (event)
|
|
KeSignalEvent(event);
|
|
return true;
|
|
}
|
|
|
|
KSTATUS KeStartWorkQueue(void) {
|
|
long state = _InterlockedCompareExchange(&gWorkQueueStartState, 1, 0);
|
|
if (state == 2)
|
|
return STATUS_SUCCESS;
|
|
if (state == 1) {
|
|
while (_InterlockedCompareExchange(&gWorkQueueStartState, 2, 2) == 1)
|
|
KeYield();
|
|
return KeIsWorkQueueStarted() ? STATUS_SUCCESS : STATUS_FAILURE;
|
|
}
|
|
|
|
LIST_INIT(&gWorkQueueList);
|
|
_InterlockedExchange(&gWorkQueueListReady, 1);
|
|
|
|
KSTATUS s = KeEventCreate(EventAutoReset, 0, &gWorkQueueEvent);
|
|
if (KSUCCESS(s)) {
|
|
THREAD* worker = (THREAD*)0;
|
|
s = KeCreateKernelThread((uintptr_t)KiWorkQueueWorker,
|
|
0,
|
|
PriorityNormal,
|
|
&worker);
|
|
// Persistent system worker; keep the creator reference for kernel life.
|
|
(void)worker;
|
|
}
|
|
|
|
if (KSUCCESS(s)) {
|
|
_InterlockedExchange(&gWorkQueueStartState, 2);
|
|
KeSignalEvent(gWorkQueueEvent);
|
|
} else {
|
|
_InterlockedExchange(&gWorkQueueStartState, 0);
|
|
}
|
|
return s;
|
|
}
|