123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601 |
- /*++
- Copyright (c) 2013 Minoca Corp.
- This file is licensed under the terms of the GNU General Public License
- version 3. Alternative licensing terms are available. Contact
- info@minocacorp.com for details. See the LICENSE file at the root of this
- project for complete licensing information.
- Module Name:
- dpc.c
- Abstract:
- This module implements support for Deferred Procedure Calls.
- Author:
- Evan Green 23-Jan-2013
- Environment:
- Kernel
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- #include <minoca/kernel/kernel.h>
- #include "kep.h"
- //
- // ---------------------------------------------------------------- Definitions
- //
- #define DPC_ALLOCATION_TAG 0x21637044 // '!cpD'
- //
- // Define the default initial entropy mask.
- //
- #define DPC_ENTROPY_MASK_DEFAULT 0x0000001F
- //
- // ------------------------------------------------------ Data Type Definitions
- //
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- VOID
- KepQueueDpc (
- PDPC Dpc,
- PPROCESSOR_BLOCK Processor
- );
- //
- // -------------------------------------------------------------------- Globals
- //
- //
- // Define how often a DPC is timestamped to add entropy to the system. If all
- // the bits in the mask are zero of the processor's DPC count, then the DPC
- // is timestamped and entropy is added. This is a relatively heavy operation,
- // so it shouldn't occur too often.
- //
- UINTN KeDpcEntropyMask = DPC_ENTROPY_MASK_DEFAULT;
- //
- // ------------------------------------------------------------------ Functions
- //
- KERNEL_API
- PDPC
- KeCreateDpc (
- PDPC_ROUTINE DpcRoutine,
- PVOID UserData
- )
- /*++
- Routine Description:
- This routine creates a new DPC with the given routine and context data.
- Arguments:
- DpcRoutine - Supplies a pointer to the routine to call when the DPC fires.
- UserData - Supplies a context pointer that can be passed to the routine via
- the DPC when it is called.
- Return Value:
- Returns a pointer to the allocated and initialized (but not queued) DPC.
- --*/
- {
- PDPC Dpc;
- Dpc = MmAllocateNonPagedPool(sizeof(DPC), DPC_ALLOCATION_TAG);
- if (Dpc == NULL) {
- return NULL;
- }
- RtlZeroMemory(Dpc, sizeof(DPC));
- Dpc->DpcRoutine = DpcRoutine;
- Dpc->UserData = UserData;
- return Dpc;
- }
- KERNEL_API
- VOID
- KeDestroyDpc (
- PDPC Dpc
- )
- /*++
- Routine Description:
- This routine destroys a DPC. It will cancel the DPC if it is queued, and
- wait for it to finish if it is running. This routine must be called from
- low level.
- Arguments:
- Dpc - Supplies a pointer to the DPC to destroy.
- Return Value:
- None.
- --*/
- {
- ASSERT(KeGetRunLevel() == RunLevelLow);
- if (!KSUCCESS(KeCancelDpc(Dpc))) {
- KeFlushDpc(Dpc);
- }
- MmFreeNonPagedPool(Dpc);
- return;
- }
- KERNEL_API
- VOID
- KeQueueDpc (
- PDPC Dpc
- )
- /*++
- Routine Description:
- This routine queues a DPC on the current processor.
- Arguments:
- Dpc - Supplies a pointer to the DPC to queue.
- Return Value:
- None.
- --*/
- {
- KepQueueDpc(Dpc, NULL);
- return;
- }
- KERNEL_API
- VOID
- KeQueueDpcOnProcessor (
- PDPC Dpc,
- ULONG ProcessorNumber
- )
- /*++
- Routine Description:
- This routine queues a DPC on the given processor.
- Arguments:
- Dpc - Supplies a pointer to the DPC to queue.
- ProcessorNumber - Supplies the processor number of the processor to queue
- the DPC on.
- Return Value:
- None.
- --*/
- {
- ASSERT(ProcessorNumber < KeGetActiveProcessorCount());
- KepQueueDpc(Dpc, KeProcessorBlocks[ProcessorNumber]);
- return;
- }
- KERNEL_API
- KSTATUS
- KeCancelDpc (
- PDPC Dpc
- )
- /*++
- Routine Description:
- This routine attempts to cancel a DPC that has been queued.
- Arguments:
- Dpc - Supplies a pointer to the DPC to cancel.
- Return Value:
- STATUS_SUCCESS if the DPC was successfully pulled out of a queue.
- STATUS_TOO_LATE if the DPC has already started running.
- --*/
- {
- BOOL Enabled;
- ULONG Processor;
- PPROCESSOR_BLOCK ProcessorBlock;
- ULONG ProcessorCount;
- KSTATUS Status;
- ProcessorCount = KeGetActiveProcessorCount();
- Status = STATUS_TOO_LATE;
- //
- // Attempt to chase the DPC around whichever processor it's running on as
- // long as it's queued on a processor's list.
- //
- while ((Dpc->UseCount != 0) &&
- ((Dpc->Flags & DPC_FLAG_QUEUED_ON_PROCESSOR) != 0)) {
- Processor = Dpc->Processor;
- if (Processor >= ProcessorCount) {
- KeCrashSystem(CRASH_DPC_FAILURE,
- DpcCrashReasonCorrupt,
- (UINTN)Dpc,
- Processor,
- ProcessorCount);
- }
- //
- // Grab the DPC lock for the processor the DPC is on. If the DPC is
- // still active for that same processor and is still on the queue, pull
- // it off the queue. It may have been pulled off the processor's DPC
- // list and be on a local list for execution. If that's the case, then
- // it is too late to cancel the DPC.
- //
- ProcessorBlock = KeProcessorBlocks[Processor];
- Enabled = ArDisableInterrupts();
- KeAcquireSpinLock(&(ProcessorBlock->DpcLock));
- if ((Dpc->UseCount != 0) &&
- (Dpc->Processor == Processor) &&
- ((Dpc->Flags & DPC_FLAG_QUEUED_ON_PROCESSOR) != 0)) {
- LIST_REMOVE(&(Dpc->ListEntry));
- Dpc->Flags &= ~DPC_FLAG_QUEUED_ON_PROCESSOR;
- Dpc->ListEntry.Next = NULL;
- Status = STATUS_SUCCESS;
- }
- KeReleaseSpinLock(&(ProcessorBlock->DpcLock));
- if (Enabled != FALSE) {
- ArEnableInterrupts();
- }
- //
- // If the DPC was successfully pulled off the queue, decrement its use
- // count and return successfully.
- //
- if (KSUCCESS(Status)) {
- RtlAtomicAdd32(&(Dpc->UseCount), -1);
- break;
- }
- }
- return Status;
- }
- KERNEL_API
- VOID
- KeFlushDpc (
- PDPC Dpc
- )
- /*++
- Routine Description:
- This routine does not return until the given DPC is out of the system. This
- means that the DPC is neither queued nor running. It's worth noting that
- this routine busy spins at dispatch level, and should therefore be used
- only sparingly. This routine can only be called from low level.
- Arguments:
- Dpc - Supplies a pointer to the DPC to wait for.
- Return Value:
- None.
- --*/
- {
- //
- // If the runlevel were dispatch or higher and the DPC was queued on this
- // processor, it would never run. It's OK if the runlevel is dispatch and
- // the DPC is queued on another processor.
- //
- ASSERT((KeGetRunLevel() == RunLevelLow) ||
- ((KeGetRunLevel() == RunLevelDispatch) &&
- (Dpc->Processor != KeGetCurrentProcessorNumber())));
- while (Dpc->UseCount != 0) {
- ArProcessorYield();
- }
- return;
- }
- VOID
- KepExecutePendingDpcs (
- VOID
- )
- /*++
- Routine Description:
- This routine executes any pending DPCs on the current processor. This
- routine should only be executed internally by the scheduler. It must be
- called at dispatch level. Interrupts must be disabled upon entry, but will
- be enabled on exit.
- Arguments:
- None.
- Return Value:
- None.
- --*/
- {
- PLIST_ENTRY CurrentEntry;
- PDPC Dpc;
- LIST_ENTRY LocalList;
- CYCLE_ACCOUNT PreviousPeriod;
- PPROCESSOR_BLOCK ProcessorBlock;
- ASSERT(KeGetRunLevel() == RunLevelDispatch);
- ProcessorBlock = KeGetCurrentProcessorBlock();
- //
- // Return immediately if the list is empty.
- //
- if (LIST_EMPTY(&(ProcessorBlock->DpcList)) != FALSE) {
- ArEnableInterrupts();
- return;
- }
- PreviousPeriod = KeBeginCycleAccounting(CycleAccountInterrupt);
- INITIALIZE_LIST_HEAD(&LocalList);
- //
- // Acquire the lock long enough to move the list off of the processor block
- // list and mark that each entry is no longer queued on said list.
- //
- KeAcquireSpinLock(&(ProcessorBlock->DpcLock));
- if (LIST_EMPTY(&(ProcessorBlock->DpcList)) == FALSE) {
- MOVE_LIST(&(ProcessorBlock->DpcList), &LocalList);
- INITIALIZE_LIST_HEAD(&(ProcessorBlock->DpcList));
- CurrentEntry = LocalList.Next;
- while (CurrentEntry != &LocalList) {
- Dpc = LIST_VALUE(CurrentEntry, DPC, ListEntry);
- Dpc->Flags &= ~DPC_FLAG_QUEUED_ON_PROCESSOR;
- CurrentEntry = CurrentEntry->Next;
- }
- }
- KeReleaseSpinLock(&(ProcessorBlock->DpcLock));
- ArEnableInterrupts();
- //
- // Set the clock to periodic mode before executing the DPCs. A DPC may
- // depend on the clock making forward progress (e.g. a timeout may be
- // implemented using recent snaps of the time counter rather than querying
- // the hardware directly).
- //
- if (LIST_EMPTY(&LocalList) == FALSE) {
- KepSetClockToPeriodic(ProcessorBlock);
- }
- //
- // Now execute all pending DPCs.
- //
- ASSERT(ProcessorBlock->DpcInProgress == NULL);
- while (LIST_EMPTY(&LocalList) == FALSE) {
- CurrentEntry = LocalList.Next;
- Dpc = LIST_VALUE(CurrentEntry, DPC, ListEntry);
- ProcessorBlock->DpcInProgress = Dpc;
- //
- // Pull the DPC off the local list and set it's next pointer to NULL to
- // indicate that it is not queued.
- //
- LIST_REMOVE(CurrentEntry);
- Dpc->ListEntry.Next = NULL;
- //
- // Call the DPC routine.
- //
- Dpc->DpcRoutine(Dpc);
- //
- // Decrement the use count to indicate that the system (or at least
- // this processor on this iteration) is done looking at this thing.
- //
- RtlAtomicAdd32(&(Dpc->UseCount), -1);
- //
- // Add one to the DPC counter, and potentially add entropy.
- //
- ProcessorBlock->DpcCount += 1;
- if ((ProcessorBlock->DpcCount & KeDpcEntropyMask) == 0) {
- KepAddTimePointEntropy();
- }
- }
- ProcessorBlock->DpcInProgress = NULL;
- KeBeginCycleAccounting(PreviousPeriod);
- return;
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
- VOID
- KepQueueDpc (
- PDPC Dpc,
- PPROCESSOR_BLOCK Processor
- )
- /*++
- Routine Description:
- This routine queues a DPC on the given processor. If the DPC is being
- queued on the current processor, and the current runlevel is less than or
- equal to dispatch, then the DPC routine is run immediately. This is useful
- for things like timer expiration.
- Arguments:
- Dpc - Supplies a pointer to the DPC to queue.
- Processor - Supplies a pointer to the processor block.
- Return Value:
- None.
- --*/
- {
- PPROCESSOR_BLOCK CurrentProcessor;
- BOOL Enabled;
- RUNLEVEL OldRunLevel;
- Enabled = ArDisableInterrupts();
- CurrentProcessor = KeGetCurrentProcessorBlock();
- if (Processor == NULL) {
- Processor = CurrentProcessor;
- }
- if (Dpc->ListEntry.Next != NULL) {
- KeCrashSystem(CRASH_DPC_FAILURE,
- DpcCrashReasonDoubleQueueDpc,
- (UINTN)Dpc,
- 0,
- 0);
- }
- ASSERT((Dpc->Flags & DPC_FLAG_QUEUED_ON_PROCESSOR) == 0);
- if (Dpc->DpcRoutine == NULL) {
- KeCrashSystem(CRASH_DPC_FAILURE,
- DpcCrashReasonNullRoutine,
- (UINTN)Dpc,
- 0,
- 0);
- }
- //
- // Run the DPC directly if it's on the current processor and the runlevel
- // is at or below dispatch.
- //
- if ((Processor == CurrentProcessor) &&
- (Processor->RunLevel <= RunLevelDispatch) &&
- (Enabled != FALSE)) {
- RtlAtomicAdd32(&(Dpc->UseCount), 1);
- OldRunLevel = KeRaiseRunLevel(RunLevelDispatch);
- if (Enabled != FALSE) {
- ArEnableInterrupts();
- }
- Dpc->DpcRoutine(Dpc);
- KeLowerRunLevel(OldRunLevel);
- RtlAtomicAdd32(&(Dpc->UseCount), -1);
- //
- // Really queue the DPC on the destination processor.
- //
- } else {
- RtlAtomicAdd32(&(Dpc->UseCount), 1);
- Dpc->Processor = Processor->ProcessorNumber;
- KeAcquireSpinLock(&(Processor->DpcLock));
- INSERT_BEFORE(&(Dpc->ListEntry), &(Processor->DpcList));
- Dpc->Flags |= DPC_FLAG_QUEUED_ON_PROCESSOR;
- KeReleaseSpinLock(&(Processor->DpcLock));
- Processor->PendingDispatchInterrupt = TRUE;
- //
- // Raise to dispatch before enabling interrupts to ensure a processor
- // switch doesn't happen before poking the clock.
- //
- OldRunLevel = RunLevelCount;
- if (CurrentProcessor->RunLevel < RunLevelDispatch) {
- OldRunLevel = KeRaiseRunLevel(RunLevelDispatch);
- }
- if (Enabled != FALSE) {
- ArEnableInterrupts();
- }
- //
- // Ensure the processor is awake to go handle a DPC.
- //
- if (Processor != CurrentProcessor) {
- KepSetClockToPeriodic(Processor);
- }
- if (OldRunLevel != RunLevelCount) {
- KeLowerRunLevel(OldRunLevel);
- }
- }
- return;
- }
|