// Deferred procedure call infrastructure. This file owns DPC queuing and execution outside hard IRQ context. #include #include #include #include #include #include #include static LIST_ENTRY gDpcQueue; static SPINLOCK gDpcLock; static volatile int32_t gDpcInitialized = 0; static volatile int32_t gDpcActive = 0; // Bound a single interrupt-exit dispatch so self-requeuing devices yield the CPU. #define KE_DPC_DISPATCH_BUDGET 64u // Initializes dpc System. void KeInitializeDpcSystem(void) { LIST_INIT(&gDpcQueue); gDpcLock = (SPINLOCK)SPINLOCK_INIT; gDpcActive = 0; gDpcInitialized = 1; } // Implements the ensure Dpc Initialized operation. 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 (NtlAtomicCompareExchange32(&dpc->Inserted, 1, 0) != 0) return false; dpc->SystemArgument1 = systemArgument1; dpc->SystemArgument2 = systemArgument2; uintptr_t flags = KeAcquireSpinlock(&gDpcLock); KeListInsertTailChecked(&gDpcQueue, &dpc->Link); KeReleaseSpinlock(&gDpcLock, flags); return true; } // Implements the drain Dpc Queue operation. 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 (NtlAtomicCompareExchange32(&gDpcActive, 1, 0) != 0) return; KIRQL oldIrql = KeGetCurrentIrql(); if (oldIrql < DISPATCH_LEVEL) KeRaiseIrql(DISPATCH_LEVEL); // The xHCI event routine already bounds its own work, but an IRQ stream // can requeue it after every dispatch. Limit the whole drain, too. The // pending queue is picked up at the next timer or device interrupt exit. for (uint32_t dispatched = 0; dispatched < KE_DPC_DISPATCH_BUDGET; dispatched++) { uintptr_t flags = KeAcquireSpinlock(&gDpcLock); LIST_ENTRY* node = KeListPopHeadChecked(&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); }