105 lines
2.9 KiB
C
105 lines
2.9 KiB
C
// ke_dpc.c - deferred procedure calls
|
|
#include <kernel/ke_dpc.h>
|
|
#include <kernel/ke_irql.h>
|
|
#include <kernel/ke_debug.h>
|
|
#include <intrin.h>
|
|
|
|
static LIST_ENTRY gDpcQueue;
|
|
static SPINLOCK gDpcLock;
|
|
static volatile long gDpcInitialized = 0;
|
|
static volatile long gDpcActive = 0;
|
|
|
|
void KeInitializeDpcSystem(void) {
|
|
LIST_INIT(&gDpcQueue);
|
|
gDpcLock = (SPINLOCK)SPINLOCK_INIT;
|
|
gDpcActive = 0;
|
|
gDpcInitialized = 1;
|
|
}
|
|
|
|
static void KiEnsureDpcInitialized(void) {
|
|
if (gDpcInitialized)
|
|
return;
|
|
|
|
// Single-CPU bring-up path: this is only a safety net for early callers.
|
|
KeInitializeDpcSystem();
|
|
}
|
|
|
|
__declspec(dllexport) void KeInitializeDpc(KDPC* dpc,
|
|
KDEFERRED_ROUTINE routine,
|
|
void* context) {
|
|
if (!dpc)
|
|
return;
|
|
|
|
LIST_INIT(&dpc->Link);
|
|
dpc->Routine = routine;
|
|
dpc->Context = context;
|
|
dpc->SystemArgument1 = (void*)0;
|
|
dpc->SystemArgument2 = (void*)0;
|
|
dpc->Inserted = 0;
|
|
}
|
|
|
|
__declspec(dllexport) bool KeInsertQueueDpc(KDPC* dpc,
|
|
void* systemArgument1,
|
|
void* systemArgument2) {
|
|
if (!dpc || !dpc->Routine)
|
|
return false;
|
|
|
|
KiEnsureDpcInitialized();
|
|
|
|
// Coalesce repeated inserts of the same KDPC. The first insert owns the
|
|
// argument pair until the DPC is popped by the dispatcher.
|
|
if (_InterlockedCompareExchange(&dpc->Inserted, 1, 0) != 0)
|
|
return false;
|
|
|
|
dpc->SystemArgument1 = systemArgument1;
|
|
dpc->SystemArgument2 = systemArgument2;
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&gDpcLock);
|
|
ListInsertTail(&gDpcQueue, &dpc->Link);
|
|
KeReleaseSpinlock(&gDpcLock, flags);
|
|
return true;
|
|
}
|
|
|
|
void KiDrainDpcQueue(void) {
|
|
if (!gDpcInitialized)
|
|
return;
|
|
|
|
// Never run DPC routines above DISPATCH_LEVEL. If a nested IRQ reaches here
|
|
// at DIRQL, leave the work pending for the outermost exit.
|
|
if (KeGetCurrentIrql() > DISPATCH_LEVEL)
|
|
return;
|
|
|
|
if (_InterlockedCompareExchange(&gDpcActive, 1, 0) != 0)
|
|
return;
|
|
|
|
KIRQL oldIrql = KeGetCurrentIrql();
|
|
if (oldIrql < DISPATCH_LEVEL)
|
|
KeRaiseIrql(DISPATCH_LEVEL);
|
|
|
|
for (;;) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gDpcLock);
|
|
LIST_ENTRY* node = ListPopHead(&gDpcQueue);
|
|
if (!node) {
|
|
KeReleaseSpinlock(&gDpcLock, flags);
|
|
break;
|
|
}
|
|
|
|
KDPC* dpc = CONTAINING_RECORD(node, KDPC, Link);
|
|
void* arg1 = dpc->SystemArgument1;
|
|
void* arg2 = dpc->SystemArgument2;
|
|
dpc->SystemArgument1 = (void*)0;
|
|
dpc->SystemArgument2 = (void*)0;
|
|
dpc->Inserted = 0;
|
|
KeReleaseSpinlock(&gDpcLock, flags);
|
|
|
|
KeAssertIrql(DISPATCH_LEVEL);
|
|
dpc->Routine(dpc, dpc->Context, arg1, arg2);
|
|
KeAssertIrql(DISPATCH_LEVEL);
|
|
}
|
|
|
|
gDpcActive = 0;
|
|
|
|
if (oldIrql < DISPATCH_LEVEL)
|
|
KeLowerIrql(oldIrql);
|
|
}
|