/*++ Copyright (c) 1989-1992 Microsoft Corporation Module Name: miscc.c Abstract: This module implements machine independent miscellaneous kernel functions. Author: David N. Cutler (davec) 13-May-1989 Environment: Kernel mode only. Revision History: --*/ #include "ki.h" #pragma alloc_text(PAGE, KeAddSystemServiceTable) #pragma alloc_text(PAGE, KeRemoveSystemServiceTable) #pragma alloc_text(PAGE, KeQueryActiveProcessors) #pragma alloc_text(PAGE, KeQueryLogicalProcessorInformation) #pragma alloc_text(PAGELK, KiCalibrateTimeAdjustment) #if !defined(_AMD64_) ULONGLONG KeQueryInterruptTime ( VOID ) /*++ Routine Description: This function returns the current interrupt time by determining when the time is stable and then returning its value. Arguments: CurrentTime - Supplies a pointer to a variable that will receive the current system time. Return Value: None. --*/ { LARGE_INTEGER CurrentTime; KiQueryInterruptTime(&CurrentTime); return CurrentTime.QuadPart; } VOID KeQuerySystemTime ( OUT PLARGE_INTEGER CurrentTime ) /*++ Routine Description: This function returns the current system time by determining when the time is stable and then returning its value. Arguments: CurrentTime - Supplies a pointer to a variable that will receive the current system time. Return Value: None. --*/ { KiQuerySystemTime(CurrentTime); return; } VOID KeQueryTickCount ( OUT PLARGE_INTEGER CurrentCount ) /*++ Routine Description: This function returns the current tick count by determining when the count is stable and then returning its value. Arguments: CurrentCount - Supplies a pointer to a variable that will receive the current tick count. Return Value: None. --*/ { KiQueryTickCount(CurrentCount); return; } #endif ULONG KeQueryTimeIncrement ( VOID ) /*++ Routine Description: This function returns the time increment value in 100ns units. This is the value that is added to the system time at each interval clock interrupt. Arguments: None. Return Value: The time increment value is returned as the function value. --*/ { return KeMaximumIncrement; } VOID KeEnableInterrupts ( IN BOOLEAN Enable ) /*++ Routine Description: This function enables interrupts based on the specified enable state. Arguments: Enable - Supplies a boolean value that determines whether interrupts are to be enabled. Return Value: None. --*/ { if (Enable != FALSE) { _enable(); } return; } VOID KeSetDmaIoCoherency ( IN ULONG Attributes ) /*++ Routine Description: This function sets (enables/disables) DMA I/O coherency with data caches. Arguments: Attributes - Supplies the set of DMA I/O coherency attributes for the host system. Return Value: None. --*/ { KiDmaIoCoherency = Attributes; } #if defined(_AMD64_) || defined(_X86_) #pragma alloc_text(INIT, KeSetProfileIrql) VOID KeSetProfileIrql ( IN KIRQL ProfileIrql ) /*++ Routine Description: This function sets the profile IRQL. N.B. There are only two valid values for the profile IRQL which are PROFILE_LEVEL and HIGH_LEVEL. Arguments: Irql - Supplies the synchronization IRQL value. Return Value: None. --*/ { ASSERT((ProfileIrql == PROFILE_LEVEL) || (ProfileIrql == HIGH_LEVEL)); KiProfileIrql = ProfileIrql; } #endif VOID KeSetSystemTime ( IN PLARGE_INTEGER NewTime, OUT PLARGE_INTEGER OldTime, IN BOOLEAN AdjustInterruptTime, IN PLARGE_INTEGER HalTimeToSet OPTIONAL ) /*++ Routine Description: This function sets the system time to the specified value and updates timer queue entries to reflect the difference between the old system time and the new system time. Arguments: NewTime - Supplies a pointer to a variable that specifies the new system time. OldTime - Supplies a pointer to a variable that will receive the previous system time. AdjustInterruptTime - If TRUE the amount of time being adjusted is also applied to InterruptTime and TickCount. HalTimeToSet - Supplies an optional time that if specified is to be used to set the time in the realtime clock. Return Value: None. --*/ { LIST_ENTRY AbsoluteListHead; LIST_ENTRY ExpiredListHead; ULONG Index; PLIST_ENTRY ListHead; PLIST_ENTRY NextEntry; KIRQL OldIrql1; KIRQL OldIrql2; LARGE_INTEGER TimeDelta; TIME_FIELDS TimeFields; PKTIMER Timer; ASSERT((NewTime->HighPart & 0xf0000000) == 0); ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); // // If a realtime clock value is specified, then convert the time value // to time fields. // if (ARGUMENT_PRESENT(HalTimeToSet)) { RtlTimeToTimeFields(HalTimeToSet, &TimeFields); } // // Set affinity to the processor that keeps the system time, raise IRQL // to dispatcher level and lock the dispatcher database, then raise IRQL // to HIGH_LEVEL to synchronize with the clock interrupt routine. // KeSetSystemAffinityThread((KAFFINITY)1); KiLockDispatcherDatabase(&OldIrql1); KeRaiseIrql(HIGH_LEVEL, &OldIrql2); // // Save the previous system time, set the new system time, and set // the realtime clock, if a time value is specified. // KiQuerySystemTime(OldTime); SharedUserData->SystemTime.High2Time = NewTime->HighPart; SharedUserData->SystemTime.LowPart = NewTime->LowPart; SharedUserData->SystemTime.High1Time = NewTime->HighPart; if (ARGUMENT_PRESENT(HalTimeToSet)) { ExCmosClockIsSane = HalSetRealTimeClock(&TimeFields); } // // Compute the difference between the previous system time and the new // system time. // TimeDelta.QuadPart = NewTime->QuadPart - OldTime->QuadPart; // // Update the boot time to reflect the delta. This keeps time based // on boot time constant // KeBootTime.QuadPart = KeBootTime.QuadPart + TimeDelta.QuadPart; // // Track the overall bias applied to the boot time. // KeBootTimeBias = KeBootTimeBias + TimeDelta.QuadPart; // // Lower IRQL to dispatch level and if needed adjust the physical // system interrupt time. // KeLowerIrql(OldIrql2); if (AdjustInterruptTime) { // // Adjust the physical time of the system // AdjustInterruptTime = KeAdjustInterruptTime (TimeDelta.QuadPart); } // // If the physical interrupt time of the system was not adjusted, // recompute any absolute timers in the system for the new // system time. // if (!AdjustInterruptTime) { // // Remove all absolute timers from the timer queue so their due time // can be recomputed. // InitializeListHead(&AbsoluteListHead); for (Index = 0; Index < TIMER_TABLE_SIZE; Index += 1) { ListHead = &KiTimerTableListHead[Index]; NextEntry = ListHead->Flink; while (NextEntry != ListHead) { Timer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry); NextEntry = NextEntry->Flink; if (Timer->Header.Absolute != FALSE) { RemoveEntryList(&Timer->TimerListEntry); InsertTailList(&AbsoluteListHead, &Timer->TimerListEntry); } } } // // Recompute the due time and reinsert all absolute timers in the timer // tree. If a timer has already expired, then insert the timer in the // expired timer list. // InitializeListHead(&ExpiredListHead); while (AbsoluteListHead.Flink != &AbsoluteListHead) { Timer = CONTAINING_RECORD(AbsoluteListHead.Flink, KTIMER, TimerListEntry); KiRemoveTreeTimer(Timer); Timer->DueTime.QuadPart -= TimeDelta.QuadPart; if (KiReinsertTreeTimer(Timer, Timer->DueTime) == FALSE) { Timer->Header.Inserted = TRUE; InsertTailList(&ExpiredListHead, &Timer->TimerListEntry); } } // // If any of the attempts to reinsert a timer failed, then timers have // already expired and must be processed. // // N.B. The following function returns with the dispatcher database // unlocked. // KiTimerListExpire(&ExpiredListHead, OldIrql1); } else { KiUnlockDispatcherDatabase(OldIrql1); } // // Set affinity back to its original value. // KeRevertToUserAffinityThread(); return; } BOOLEAN KeAdjustInterruptTime ( IN LONGLONG TimeDelta ) /*++ Routine Description: This function moves the physical interrupt time of the system foreward by the specified time delta after a system wake has occurred. Arguments: TimeDelta - Supplies the time delta to be added to the interrupt time, tick count and the perforamnce counter in 100ns units. Return Value: None. --*/ { ADJUST_INTERRUPT_TIME_CONTEXT Adjust; // // Time can only be moved forward. // if (TimeDelta < 0) { return FALSE; } else { Adjust.KiNumber = KeNumberProcessors; Adjust.HalNumber = KeNumberProcessors; Adjust.Adjustment = (ULONGLONG) TimeDelta; Adjust.Barrier = 1; KeIpiGenericCall((PKIPI_BROADCAST_WORKER)KiCalibrateTimeAdjustment, (ULONG_PTR)(&Adjust)); return TRUE; } } VOID KiCalibrateTimeAdjustment ( PADJUST_INTERRUPT_TIME_CONTEXT Adjust ) /*++ Routine Description: This function calibrates the adjustment of time on all processors. Arguments: Adjust - Supplies the operation context. Return Value: None. --*/ { ULONG cl; ULONG divisor; BOOLEAN Enable; LARGE_INTEGER InterruptTime; ULARGE_INTEGER li; LARGE_INTEGER NewTickCount; ULONG NewTickOffset; LARGE_INTEGER PerfCount; LARGE_INTEGER PerfFreq; LARGE_INTEGER SetTime; // // As each processor arrives, decrement the remaining processor count. If // this is the last processor to arrive, then compute the time change, and // signal all processor when to apply the performance counter change. // if (InterlockedDecrement((PLONG)&Adjust->KiNumber)) { Enable = KeDisableInterrupts(); // // It is possible to deadlock here if one or more of the // other processors gets and processes a freeze request // while this processor has interrupts disabled. Poll // for IPI_FREEZE requests until all processors are known // to be in this code and hence wont be requesting a // freeze. // do { KiPollFreezeExecution(); } while (Adjust->KiNumber != (ULONG)-1); // // Wait to perform the time set // while (Adjust->Barrier) ; } else { // // Set timer expiration dpc to scan the timer queues once for any // expired timers. // KeRemoveQueueDpc(&KiTimerExpireDpc); KeInsertQueueDpc(&KiTimerExpireDpc, ULongToPtr(KiQueryLowTickCount() - TIMER_TABLE_SIZE), NULL); // // Disable interrupts and indicate that this processor is now // in final portion of this code. // Enable = KeDisableInterrupts(); InterlockedDecrement((PLONG) &Adjust->KiNumber); // // Adjust Interrupt Time. // InterruptTime.QuadPart = KeQueryInterruptTime() + Adjust->Adjustment; SetTime.QuadPart = Adjust->Adjustment; // // Get the current times // PerfCount = KeQueryPerformanceCounter(&PerfFreq); // // Compute performance counter for current SetTime // // // Multiply SetTime * PerfCount and obtain 96bit result // in cl, li.LowPart, li.HighPart. Then divide the 96bit // result by 10,000,000 to get new performance counter value. // li.QuadPart = RtlEnlargedUnsignedMultiply((ULONG)SetTime.LowPart, (ULONG)PerfFreq.LowPart).QuadPart; cl = li.LowPart; li.QuadPart = li.HighPart + RtlEnlargedUnsignedMultiply((ULONG)SetTime.LowPart, (ULONG)PerfFreq.HighPart).QuadPart; li.QuadPart = li.QuadPart + RtlEnlargedUnsignedMultiply((ULONG)SetTime.HighPart, (ULONG)PerfFreq.LowPart).QuadPart; li.HighPart = li.HighPart + SetTime.HighPart * PerfFreq.HighPart; divisor = 10000000; Adjust->NewCount.HighPart = RtlEnlargedUnsignedDivide(li, divisor, &li.HighPart); li.LowPart = cl; Adjust->NewCount.LowPart = RtlEnlargedUnsignedDivide(li, divisor, NULL); Adjust->NewCount.QuadPart += PerfCount.QuadPart; // // Compute tick count and tick offset for current InterruptTime // NewTickCount = RtlExtendedLargeIntegerDivide(InterruptTime, KeMaximumIncrement, &NewTickOffset); // // Apply changes to InterruptTime, TickCount, TickOffset, and the // performance counter. // KiTickOffset = KeMaximumIncrement - NewTickOffset; KeInterruptTimeBias += Adjust->Adjustment; SharedUserData->TickCount.High2Time = NewTickCount.HighPart; #if defined(_WIN64) SharedUserData->TickCountQuad = NewTickCount.QuadPart; #else SharedUserData->TickCount.LowPart = NewTickCount.LowPart; SharedUserData->TickCount.High1Time = NewTickCount.HighPart; #endif #if defined(_IA64_) KeTickCount = NewTickCount; #elif defined(_X86_) KeTickCount.High2Time = NewTickCount.HighPart; KeTickCount.LowPart = NewTickCount.LowPart; KeTickCount.High1Time = NewTickCount.HighPart; #endif SharedUserData->InterruptTime.High2Time = InterruptTime.HighPart; SharedUserData->InterruptTime.LowPart = InterruptTime.LowPart; SharedUserData->InterruptTime.High1Time = InterruptTime.HighPart; // // Apply the performance counter change. // Adjust->Barrier = 0; } HalCalibratePerformanceCounter((LONG volatile *)&Adjust->HalNumber, (ULONGLONG) Adjust->NewCount.QuadPart); KeEnableInterrupts(Enable); } VOID KeSetTimeIncrement ( IN ULONG MaximumIncrement, IN ULONG MinimumIncrement ) /*++ Routine Description: This function sets the time increment value in 100ns units. This value is added to the system time at each interval clock interrupt. Arguments: MaximumIncrement - Supplies the maximum time between clock interrupts in 100ns units supported by the host HAL. MinimumIncrement - Supplies the minimum time between clock interrupts in 100ns units supported by the host HAL. Return Value: None. --*/ { KeMaximumIncrement = MaximumIncrement; KeMinimumIncrement = max(MinimumIncrement, 10 * 1000); KeTimeAdjustment = MaximumIncrement; KeTimeIncrement = MaximumIncrement; KiTickOffset = MaximumIncrement; #if defined(_IA64_) KiMaxIntervalPerTimerInterrupt = MaximumIncrement * (TIMER_TABLE_SIZE - 1); #endif } BOOLEAN KeAddSystemServiceTable( IN PULONG_PTR Base, IN PULONG Count OPTIONAL, IN ULONG Limit, IN PUCHAR Number, IN ULONG Index ) /*++ Routine Description: This function allows the caller to add a system service table to the system Arguments: Base - Supplies the address of the system service table dispatch table. Count - Supplies an optional pointer to a table of per system service counters. Limit - Supplies the limit of the service table. Services greater than or equal to this limit will fail. Arguments - Supplies the address of the argument count table. Index - Supplies index of the service table. Return Value: TRUE - The operation was successful. FALSE - the operation failed. A service table is already bound to the specified location, or the specified index is larger than the maximum allowed index. --*/ { PAGED_CODE(); // // If a system service table is already defined for the specified // index, then return FALSE. Otherwise, establish the new system // service table. // if ((Index > NUMBER_SERVICE_TABLES - 1) || (KeServiceDescriptorTable[Index].Base != NULL) || (KeServiceDescriptorTableShadow[Index].Base != NULL)) { return FALSE; } else { // // If the service table index is equal to the Win32 table, then // only update the shadow system service table. Otherwise, both // the shadow and static system service tables are updated. // KeServiceDescriptorTableShadow[Index].Base = Base; KeServiceDescriptorTableShadow[Index].Count = Count; KeServiceDescriptorTableShadow[Index].Limit = Limit; // // The global pointer associated with the table base is // placed just before the service table. // #if defined(_IA64_) KeServiceDescriptorTableShadow[Index].TableBaseGpOffset = (LONG)(*(Base-1) - (ULONG_PTR)Base); #endif KeServiceDescriptorTableShadow[Index].Number = Number; if (Index != 1) { KeServiceDescriptorTable[Index].Base = Base; KeServiceDescriptorTable[Index].Count = Count; KeServiceDescriptorTable[Index].Limit = Limit; #if defined(_IA64_) KeServiceDescriptorTable[Index].TableBaseGpOffset = (LONG)(*(Base-1) - (ULONG_PTR)Base); #endif KeServiceDescriptorTable[Index].Number = Number; } return TRUE; } } BOOLEAN KeRemoveSystemServiceTable( IN ULONG Index ) /*++ Routine Description: This function allows the caller to remove a system service table from the system. This can only be called at system shutdown. Arguments: Index - Supplies index of the service table. Return Value: TRUE - The operation was successful. FALSE - the operation failed. A service table is is not bound or is illegal to remove --*/ { PAGED_CODE(); if ((Index > NUMBER_SERVICE_TABLES - 1) || ((KeServiceDescriptorTable[Index].Base == NULL) && (KeServiceDescriptorTableShadow[Index].Base == NULL))) { return FALSE; } else { KeServiceDescriptorTableShadow[Index].Base = NULL; KeServiceDescriptorTableShadow[Index].Count = 0; KeServiceDescriptorTableShadow[Index].Limit = 0; #if defined(_IA64_) KeServiceDescriptorTableShadow[Index].TableBaseGpOffset = 0; #endif KeServiceDescriptorTableShadow[Index].Number = 0; if (Index != 1) { KeServiceDescriptorTable[Index].Base = NULL; KeServiceDescriptorTable[Index].Count = 0; KeServiceDescriptorTable[Index].Limit = 0; #if defined(_IA64_) KeServiceDescriptorTable[Index].TableBaseGpOffset = 0; #endif KeServiceDescriptorTable[Index].Number = 0; } return TRUE; } } KAFFINITY KeQueryActiveProcessors( VOID ) /*++ Routine Description: This function returns the current set of active processors in the system. Arguments: None. Return Value: KAFFINITY bitmask representing the set of active processors --*/ { PAGED_CODE(); return(KeActiveProcessors); } NTSTATUS KeQueryLogicalProcessorInformation( OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnedLength ) /*++ Routine Description: This function returns information about the logical processors in the system and is invoked via NtQuerySystemInformation. It runs in an existing try/except block. A group of structures will be written to the output buffer describing groups of logical processors, and the relationship between them. Currently it returns information about the logical processors that are produced by individual processor cores and the logical processors associated with individual NUMA nodes. The former makes it possible for an application to understand the relationship between logical processors and physical processors in hyperthreading scenarios which supports some licensing and performance optimization scenarios. This function may be extended in the future to support multicore processors and platform caches. Arguments: SystemInformation - A pointer to a buffer which receives the specified information. The buffer will be will be filled by this function with SYSTEM_LOGICAL_PROCESSOR_INFORMATION structures. SystemInformationLength - Specifies the length in bytes of the system information buffer. ReturnLength - A pointer which receives the number of bytes necessary to return all of the information records available. Return Value: NTSTATUS --*/ { PSYSTEM_LOGICAL_PROCESSOR_INFORMATION Output; KAFFINITY ActiveProcessors; KAFFINITY Mask; PKPRCB Prcb; NTSTATUS Status = STATUS_SUCCESS; ULONG CurrentLength; ULONG i; UCHAR Flags; #if defined(KE_MULTINODE) PKNODE Node; #endif PAGED_CODE(); CurrentLength = 0; Output = SystemInformation; ActiveProcessors = KeActiveProcessors; i = 0; for (; ActiveProcessors; ActiveProcessors >>= 1 , i++) { if ((ActiveProcessors & 1) == 0) { continue; } Prcb = KiProcessorBlock[i]; Flags = 0; #if defined(NT_SMT) // // Ignore logical processors that are not the master of their // thread set. As a result, only one PRCB per physical // processor will be further interrogated. // if (Prcb != Prcb->MultiThreadSetMaster) { continue; } Mask = Prcb->MultiThreadProcessorSet; // // Determine if this physical processor is exposing multiple // logical processors. If so, mark it as a SMT relationship. // if (Prcb->SetMember != Mask) { Flags = LTP_PC_SMT; } #else Mask = Prcb->SetMember; #endif CurrentLength += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); if (CurrentLength <= SystemInformationLength) { Output->ProcessorMask = Mask; Output->Relationship = RelationProcessorCore; Output->Reserved[0] = Output->Reserved[1] = 0; Output->ProcessorCore.Flags = Flags; Output++; } else { Status = STATUS_INFO_LENGTH_MISMATCH; } } // // Add records indicating the association of logical processors // with NUMA nodes. // #if defined(KE_MULTINODE) for (i = 0; i < KeNumberNodes; i++) { Node = KeNodeBlock[i]; if (Node->ProcessorMask == 0) { continue; } CurrentLength += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); if (CurrentLength <= SystemInformationLength) { Output->ProcessorMask = Node->ProcessorMask; Output->Relationship = RelationNumaNode; Output->Reserved[0] = Output->Reserved[1] = 0; Output->NumaNode.NodeNumber = i; Output++; } else { Status = STATUS_INFO_LENGTH_MISMATCH; } } #else CurrentLength += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); if (CurrentLength <= SystemInformationLength) { Output->ProcessorMask = KeActiveProcessors; Output->Relationship = RelationNumaNode; Output->NumaNode.NodeNumber = 0; Output++; } else { Status = STATUS_INFO_LENGTH_MISMATCH; } #endif // // Additional topology information would be added here such as // multicore and platform caches. // // // Always return how long the buffer needed to be for the API to // be successful. // *ReturnedLength = CurrentLength; return Status; } #undef KeIsAttachedProcess BOOLEAN KeIsAttachedProcess( VOID ) /*++ Routine Description: This function determines if the current thread is attached to a process. Arguments: None. Return Value: TRUE is returned if the current thread is attached to a process. Otherwise, FALSE is returned. --*/ { return KiIsAttachedProcess() ? TRUE : FALSE; } ULONG KeGetRecommendedSharedDataAlignment ( VOID ) /*++ Routine Description: This function returns the size of the largest cache line in the system. This value should be used as a recommended alignment / granularity for shared data. Arguments: None. Return Value: The size of the largest cache line in the system is returned as the function value. --*/ { return KeLargestCacheLine; } PKPRCB KeGetPrcb( ULONG ProcessorNumber ) /*++ Routine Description: This function returns the address of the Processor Control Block (PRCB) for the specified processor. Arguments: ProcessorNumber - Supplies the number of the processor the PRCB is to be returned for. Return Value: Returns the address of the requested PRCB or NULL if ProcessorNumber is not valid. --*/ { ASSERT(ProcessorNumber < MAXIMUM_PROCESSORS); if (ProcessorNumber < (ULONG)KeNumberProcessors) { return KiProcessorBlock[ProcessorNumber]; } return NULL; } NTSTATUS KeCopySafe( VOID UNALIGNED *Destination, CONST VOID UNALIGNED *Source, SIZE_T Length ) /*++ Routine Description: This function attempts to safely copy a block of memory. If an exception occurs the exception status is returned. Arguments: Destination - Supplies a pointer to the destination memory. Source - Supplies a pointer to the source memory. Length - Supplies the size of memory in bytes to be copied. Return Value: Return the status of the copy. --*/ { NTSTATUS Status = STATUS_SUCCESS; try { RtlCopyMemory(Destination, Source, Length); } __except(EXCEPTION_EXECUTE_HANDLER) { Status = _exception_code(); } return Status; } typedef struct _KNMI_HANDLER_CALLBACK { struct _KNMI_HANDLER_CALLBACK * Next; PNMI_CALLBACK Callback; PVOID Context; PVOID Handle; } KNMI_HANDLER_CALLBACK, *PKNMI_HANDLER_CALLBACK; PKNMI_HANDLER_CALLBACK KiNmiCallbackListHead; KSPIN_LOCK KiNmiCallbackListLock; BOOLEAN KiHandleNmi( VOID ) /*++ Routine Description: This routine is called to process the list of registered Non-Maskable- Interrupt (NMI) handlers in the system. This routine is called from the NMI interrupt vector, the IRQL is unknown and must be treated as if at HIGH_LEVEL. Neither this function or any called function can alter system IRQL. The list of handlers must be edited in such a way that it is always valid. This routine cannot acquire a lock before transiting the list. Arguments: None. Return Value: Returns TRUE is any handler on the list claims to have handled the interrupt, FALSE otherwise. --*/ { BOOLEAN Handled; PKNMI_HANDLER_CALLBACK Handler; Handler = KiNmiCallbackListHead; Handled = FALSE; while (Handler) { Handled |= Handler->Callback(Handler->Context, Handled); Handler = Handler->Next; } return Handled; } PVOID KeRegisterNmiCallback( PNMI_CALLBACK CallbackRoutine, PVOID Context ) /*++ Routine Description: This routine is called to add a callback to the list of Non-Maskable- Interrupt (NMI) handlers. This routine must be called at IRQL < DISPATCH_LEVEL. List insertion must be such that the list is ALWAYS valid, an NMI could occur during insertion and the NMI handler must be able to safely transit the list. Arguments: CallbackRoutine supplies a pointer to the routine to be called on NMI. Context supplies an arbitary value which will be passed to the CallbackRoutine. Return Value: Returns an arbitary handle that must be passed to KeDeregisterNmiCallback or NULL if registration was unsuccessful. --*/ { PKNMI_HANDLER_CALLBACK Handler; PKNMI_HANDLER_CALLBACK Next; KIRQL OldIrql; ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); // // Allocate memory for the callback object. // Handler = ExAllocatePoolWithTag(NonPagedPool, sizeof(KNMI_HANDLER_CALLBACK), 'IMNK'); if (Handler == NULL) { return Handler; } // // Fill in the non-protected elements. // Handler->Callback = CallbackRoutine; Handler->Context = Context; Handler->Handle = Handler; // // Insert the handler onto the front of the list. // KeAcquireSpinLock(&KiNmiCallbackListLock, &OldIrql); Handler->Next = KiNmiCallbackListHead; // // Because the lock is held, the following can't fail but is needed // to ensure the compiler doesn't store KiNmiCallbackList before // storing Handler->Next because the NMI handler may run down this // list and does not (can not) take the lock. // Next = InterlockedCompareExchangePointer(&KiNmiCallbackListHead, Handler, Handler->Next); ASSERT(Next == Handler->Next); KeReleaseSpinLock(&KiNmiCallbackListLock, OldIrql); // // Return the address of this handler as an opaque handle. // return Handler->Handle; } NTSTATUS KeDeregisterNmiCallback( PVOID Handle ) /*++ Routine Description: This routine is called to remove a callback from the list of Non- Maskable-Interrupt callbacks. This routine must be called at IRQL < DISPATCH_LEVEL. List removal must be such that the list is ALWAYS valid, an NMI could occur during removal and the NMI handler must be able to safely transit the list. Arguments: Handle supplied an opaque handle to the callback object that was returned by KeRegisterNmiCallback. Return Value: Returns STATUS_SUCCESS if the object was successfully removed from the list. STATUS_INVALID_HANDLE otherwise. --*/ { PKNMI_HANDLER_CALLBACK Handler; PKNMI_HANDLER_CALLBACK *PreviousNext; KIRQL OldIrql; #if !defined(NT_UP) KAFFINITY ActiveProcessors; KAFFINITY CurrentAffinity; #endif ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); KeAcquireSpinLock(&KiNmiCallbackListLock, &OldIrql); // // Find the handler given the list of handlers. // // N.B. In the current implementation, the handle is the address // of the handler however this code is designed for a more opaque // handle. // PreviousNext = &KiNmiCallbackListHead; for (Handler = *PreviousNext; Handler; PreviousNext = &Handler->Next, Handler = Handler->Next) { if (Handler->Handle == Handle) { ASSERT(Handle == Handler); break; } } if ((Handler == NULL) || (Handler->Handle != Handle)) { KeReleaseSpinLock(&KiNmiCallbackListLock, OldIrql); return STATUS_INVALID_HANDLE; } // // Remove this handler from the list. // *PreviousNext = Handler->Next; KeReleaseSpinLock(&KiNmiCallbackListLock, OldIrql); // // Cycle through each processor in the system to ensure that any // NMI which has begun execution on another processor has completed // execution before releasing the memory for the NMI callback object. // #if !defined(NT_UP) ActiveProcessors = KeActiveProcessors; for (CurrentAffinity = 1; ActiveProcessors; CurrentAffinity <<= 1) { if (ActiveProcessors & CurrentAffinity) { ActiveProcessors &= ~CurrentAffinity; KeSetSystemAffinityThread(CurrentAffinity); } } KeRevertToUserAffinityThread(); #endif ExFreePoolWithTag(Handler, 'INMK'); return STATUS_SUCCESS; }