123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695 |
- /*++
- Copyright (c) 2015 Minoca Corp. All Rights Reserved
- Module Name:
- cstate.c
- Abstract:
- This module implements generic kernel support for processor C-state
- transitions.
- Author:
- Evan Green 25-Sep-2015
- Environment:
- Kernel
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- #include <minoca/kernel/kernel.h>
- #include "pmp.h"
- //
- // ---------------------------------------------------------------- Definitions
- //
- //
- // Define how many CPU idle history events to keep around, as a log2 (bit
- // shift) value.
- //
- #define PM_CSTATE_HISTORY_SHIFT 7
- //
- // ------------------------------------------------------ Data Type Definitions
- //
- /*++
- Structure Description:
- This structure defines statistics for C-state transitions on a specific
- processor.
- Members:
- EntryCount - Stores the number of times this state has been entered.
- TimeSpent - Stores the total number of time counter ticks spent in this
- state.
- --*/
- typedef struct _PM_CSTATE_STATISTICS {
- ULONGLONG EntryCount;
- ULONGLONG TimeSpent;
- } PM_CSTATE_STATISTICS, *PPM_CSTATE_STATISTICS;
- /*++
- Structure Description:
- This structure defines the internal idle state context for a particular
- processor.
- Members:
- Driver - Stores the driver information for this processor.
- History - Stores the processor's idle history.
- Statistics - Stores an array of statistics for each C-state. The first
- index is reserved for halt-only transitions. Starting with index one,
- each entry corresponds to a C-state described by the C-state driver.
- --*/
- typedef struct _PM_PROCESSOR_CSTATE_DATA {
- PM_IDLE_PROCESSOR_STATE Driver;
- PIDLE_HISTORY History;
- PPM_CSTATE_STATISTICS Statistics;
- } PM_PROCESSOR_CSTATE_DATA, *PPM_PROCESSOR_CSTATE_DATA;
- /*++
- Structure Description:
- This structure defines the global kernel idle state information.
- Members:
- Interface - Stores a pointer to the interface.
- Processors - Stores a pointer to the array of processor idle state
- structures.
- ProcessorCount - Stores the number of processors in the array.
- --*/
- typedef struct _PM_CSTATE_DATA {
- PPM_IDLE_STATE_INTERFACE Interface;
- PPM_PROCESSOR_CSTATE_DATA Processors;
- ULONG ProcessorCount;
- } PM_CSTATE_DATA, *PPM_CSTATE_DATA;
- /*++
- Structure Description:
- This structure defines the context used while the C-states are initializing
- on each processor.
- Members:
- Data - Stores a pointer to the interface data.
- FinishEvent - Stores a pointer to an event that will be signaled when the
- initialization is finished one way or another.
- Status - Stores the resulting status code of the initialization.
- --*/
- typedef struct _PM_CSTATE_INITIALIZATION_CONTEXT {
- PPM_CSTATE_DATA Data;
- PKEVENT Event;
- KSTATUS Status;
- } PM_CSTATE_INITIALIZATION_CONTEXT, *PPM_CSTATE_INITIALIZATION_CONTEXT;
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- KSTATUS
- PmpInitializeIdleStates (
- PPM_IDLE_STATE_INTERFACE Interface
- );
- VOID
- PmpInitializeProcessorIdleStatesDpc (
- PDPC Dpc
- );
- VOID
- PmpDebugPrintCstateStatistics (
- PPM_PROCESSOR_CSTATE_DATA Data
- );
- //
- // -------------------------------------------------------------------- Globals
- //
- //
- // Set this boolean to prevent entering any advanced C-states.
- //
- BOOL PmDisableCstates;
- //
- // Set this boolean to print all C-state information.
- //
- ULONG PmPrintCstateStatistics;
- //
- // Set this boolean to prevent halting at all when idle. The disable C-states
- // boolean also needs to be set to full disable any processor hardware idle
- // states.
- //
- BOOL PmDisableHalt;
- //
- // Store the global C-state context.
- //
- PPM_CSTATE_DATA PmCstateData;
- //
- // ------------------------------------------------------------------ Functions
- //
- VOID
- PmIdle (
- PPROCESSOR_BLOCK Processor
- )
- /*++
- Routine Description:
- This routine is called on a processor to go into a low power idle state. If
- no processor idle driver has been registered or processor idle states have
- been disabled, then the processor simply halts waiting for an interrupt.
- This routine is called with interrupts disabled and returns with interrupts
- enabled. This routine should only be called internally by the idle thread.
- Arguments:
- Processor - Supplies a pointer to the processor going idle.
- Return Value:
- None. This routine returns from the idle state with interrupts enabled.
- --*/
- {
- PPM_CSTATE_DATA Data;
- ULONGLONG Duration;
- ULONGLONG EndTime;
- ULONGLONG Estimate;
- ULONG Index;
- PPM_PROCESSOR_CSTATE_DATA ProcessorData;
- ULONGLONG StartTime;
- PPM_IDLE_STATE States;
- ULONG TargetState;
- Data = PmCstateData;
- //
- // If there is no C-state data, just do a halt.
- //
- if (Data == NULL) {
- if (PmDisableHalt != FALSE) {
- ArEnableInterrupts();
- } else {
- ArWaitForInterrupt();
- }
- return;
- }
- ProcessorData = &(Data->Processors[Processor->ProcessorNumber]);
- if (PmPrintCstateStatistics != 0) {
- if (RtlAtomicExchange32(&PmPrintCstateStatistics, 0) != 0) {
- for (Index = 0; Index < PmCstateData->ProcessorCount; Index += 1) {
- PmpDebugPrintCstateStatistics(
- &(PmCstateData->Processors[Index]));
- }
- }
- }
- //
- // Figure out an estimate for how long this processor will be idle by
- // looking at the average of its last few idle transitions.
- //
- Estimate = PmpIdleHistoryGetAverage(ProcessorData->History);
- //
- // Figure out the best state to go to, overshooting by one.
- //
- TargetState = 0;
- if (PmDisableCstates == FALSE) {
- States = ProcessorData->Driver.States;
- while ((TargetState < ProcessorData->Driver.StateCount) &&
- (Estimate >= States[TargetState].TargetResidency)) {
- TargetState += 1;
- }
- }
- //
- // Snap the start time, and go idle.
- //
- StartTime = HlQueryTimeCounter();
- if (TargetState == 0) {
- ProcessorData->Driver.CurrentState = PM_IDLE_STATE_HALT;
- ArWaitForInterrupt();
- ArDisableInterrupts();
- } else {
- //
- // The loop above overshot by one, so back it down, and go to the idle
- // state.
- //
- ProcessorData->Driver.CurrentState = TargetState - 1;
- Data->Interface->EnterIdleState(&(ProcessorData->Driver),
- TargetState - 1);
- //
- // The driver may have actually entered a different state.
- //
- TargetState = ProcessorData->Driver.CurrentState + 1;
- }
- EndTime = HlQueryTimeCounter();
- if (EndTime < StartTime) {
- RtlDebugPrint("CSTATE: Time went backwards from %I64x to %I64x\n",
- StartTime,
- EndTime);
- }
- //
- // Compute this last idle duration and add it as a historical datapoint.
- //
- Duration = EndTime - StartTime;
- ProcessorData->Driver.CurrentState = PM_IDLE_STATE_NONE;
- PmpIdleHistoryAddDataPoint(ProcessorData->History, Duration);
- //
- // Mark the statistics as well. The index is offset by one to make room for
- // the "halt-only" entry.
- //
- ProcessorData->Statistics[TargetState].EntryCount += 1;
- ProcessorData->Statistics[TargetState].TimeSpent += Duration;
- ArEnableInterrupts();
- return;
- }
- KSTATUS
- PmpGetSetIdleStateHandlers (
- BOOL FromKernelMode,
- PVOID Data,
- PUINTN DataSize,
- BOOL Set
- )
- /*++
- Routine Description:
- This routine gets or sets the idle state handlers. In this case the data
- pointer is used directly (so the interface structure must not disappear
- after the call). This can only be set, can only be set once, and can only
- be set from kernel mode for obvious reasons.
- Arguments:
- FromKernelMode - Supplies a boolean indicating whether or not this request
- (and the buffer associated with it) originates from user mode (FALSE)
- or kernel mode (TRUE).
- Data - Supplies a pointer to the data buffer where the data is either
- returned for a get operation or given for a set operation.
- DataSize - Supplies a pointer that on input contains the size of the
- data buffer. On output, contains the required size of the data buffer.
- Set - Supplies a boolean indicating if this is a get operation (FALSE) or
- a set operation (TRUE).
- Return Value:
- STATUS_SUCCESS if the performance state information was initialized.
- STATUS_NOT_SUPPORTED for a get operation.
- STATUS_PERMISSION_DENIED if this is a user mode request.
- STATUS_DATA_LENGTH_MISMATCH if the data size is not the size of the
- PM_IDLE_STATE_INTERFACE structure.
- STATUS_TOO_LATE if performance state handlers have already been registered.
- Other errors if the performance state runtime could not be initialized.
- --*/
- {
- if (FromKernelMode == FALSE) {
- return STATUS_PERMISSION_DENIED;
- }
- if (Set == FALSE) {
- return STATUS_NOT_SUPPORTED;
- }
- if (*DataSize != sizeof(PM_IDLE_STATE_INTERFACE)) {
- *DataSize = sizeof(PM_IDLE_STATE_INTERFACE);
- return STATUS_DATA_LENGTH_MISMATCH;
- }
- if (PmCstateData != NULL) {
- return STATUS_TOO_LATE;
- }
- return PmpInitializeIdleStates(Data);
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
- KSTATUS
- PmpInitializeIdleStates (
- PPM_IDLE_STATE_INTERFACE Interface
- )
- /*++
- Routine Description:
- This routine initializes the CPU idle state interface.
- Arguments:
- Interface - Supplies a pointer to the idle state interface.
- Return Value:
- Status code.
- --*/
- {
- UINTN AllocationSize;
- PPM_CSTATE_DATA Data;
- PDPC Dpc;
- ULONG Index;
- PM_CSTATE_INITIALIZATION_CONTEXT InitializationContext;
- ULONG ProcessorCount;
- PPM_PROCESSOR_CSTATE_DATA ProcessorData;
- KSTATUS Status;
- ASSERT(PmCstateData == NULL);
- ASSERT(KeGetRunLevel() == RunLevelLow);
- RtlZeroMemory(&InitializationContext,
- sizeof(PM_CSTATE_INITIALIZATION_CONTEXT));
- Dpc = NULL;
- ProcessorCount = KeGetActiveProcessorCount();
- AllocationSize = sizeof(PM_CSTATE_DATA) +
- (ProcessorCount * sizeof(PM_PROCESSOR_CSTATE_DATA));
- Data = MmAllocateNonPagedPool(AllocationSize, PM_ALLOCATION_TAG);
- if (Data == NULL) {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- goto InitializeIdleStatesEnd;
- }
- RtlZeroMemory(Data, AllocationSize);
- Data->Interface = Interface;
- Data->ProcessorCount = ProcessorCount;
- Data->Processors = (PPM_PROCESSOR_CSTATE_DATA)(Data + 1);
- for (Index = 0; Index < ProcessorCount; Index += 1) {
- ProcessorData = &(Data->Processors[Index]);
- ProcessorData->Driver.CurrentState = PM_IDLE_STATE_NONE;
- ProcessorData->History = PmpCreateIdleHistory(IDLE_HISTORY_NON_PAGED,
- PM_CSTATE_HISTORY_SHIFT);
- if (ProcessorData->History == NULL) {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- goto InitializeIdleStatesEnd;
- }
- }
- Dpc = KeCreateDpc(PmpInitializeProcessorIdleStatesDpc,
- &InitializationContext);
- if (Dpc == NULL) {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- goto InitializeIdleStatesEnd;
- }
- InitializationContext.Data = Data;
- InitializationContext.Event = KeCreateEvent(NULL);
- if (InitializationContext.Event == NULL) {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- goto InitializeIdleStatesEnd;
- }
- KeSignalEvent(InitializationContext.Event, SignalOptionUnsignal);
- //
- // Queue the DPC on each processor successively, starting with 0, and wait
- // for it to finish.
- //
- KeQueueDpcOnProcessor(Dpc, 0);
- KeWaitForEvent(InitializationContext.Event, FALSE, WAIT_TIME_INDEFINITE);
- Status = InitializationContext.Status;
- InitializeIdleStatesEnd:
- if (InitializationContext.Event != NULL) {
- KeDestroyEvent(InitializationContext.Event);
- }
- if (Dpc != NULL) {
- KeDestroyDpc(Dpc);
- }
- if (!KSUCCESS(Status)) {
- if (Data != NULL) {
- for (Index = 0; Index < ProcessorCount; Index += 1) {
- ProcessorData = &(Data->Processors[Index]);
- if (ProcessorData->History != NULL) {
- PmpDestroyIdleHistory(ProcessorData->History);
- }
- if (ProcessorData->Statistics != NULL) {
- MmFreeNonPagedPool(ProcessorData->Statistics);
- }
- }
- MmFreeNonPagedPool(Data);
- Data = NULL;
- }
- }
- PmCstateData = Data;
- return Status;
- }
- VOID
- PmpInitializeProcessorIdleStatesDpc (
- PDPC Dpc
- )
- /*++
- Routine Description:
- This routine initializes the processor idle state information for a
- particular processor. It then queues itself on the next processor.
- Arguments:
- Dpc - Supplies a pointer to the DPC that is running.
- Return Value:
- None.
- --*/
- {
- UINTN AllocationSize;
- PPM_CSTATE_INITIALIZATION_CONTEXT Context;
- PPROCESSOR_BLOCK Processor;
- PPM_PROCESSOR_CSTATE_DATA ProcessorData;
- KSTATUS Status;
- Processor = KeGetCurrentProcessorBlock();
- Context = Dpc->UserData;
- ProcessorData = &(Context->Data->Processors[Processor->ProcessorNumber]);
- ProcessorData->Driver.ProcessorNumber = Processor->ProcessorNumber;
- Status = Context->Data->Interface->InitializeIdleStates(
- Context->Data->Interface,
- &(ProcessorData->Driver));
- if (!KSUCCESS(Status)) {
- goto InitializeProcessorIdleStatesDpcEnd;
- }
- AllocationSize = (ProcessorData->Driver.StateCount + 1) *
- sizeof(PM_CSTATE_STATISTICS);
- ProcessorData->Statistics = MmAllocateNonPagedPool(AllocationSize,
- PM_ALLOCATION_TAG);
- if (ProcessorData->Statistics == NULL) {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- goto InitializeProcessorIdleStatesDpcEnd;
- }
- RtlZeroMemory(ProcessorData->Statistics, AllocationSize);
- Status = STATUS_SUCCESS;
- InitializeProcessorIdleStatesDpcEnd:
- //
- // If it failed or this was the last one, end now.
- //
- if ((!KSUCCESS(Status)) ||
- (Processor->ProcessorNumber == Context->Data->ProcessorCount - 1)) {
- Context->Status = Status;
- //
- // As soon as the event is signaled, none of these structures are safe
- // to touch anymore.
- //
- KeSignalEvent(Context->Event, SignalOptionSignalAll);
- //
- // This initialization was successful and this is not the last processor,
- // so queue this DPC on the next processor. Again, now the structures are
- // no longer safe to touch.
- //
- } else {
- KeQueueDpcOnProcessor(Dpc, Processor->ProcessorNumber + 1);
- }
- return;
- }
- VOID
- PmpDebugPrintCstateStatistics (
- PPM_PROCESSOR_CSTATE_DATA Data
- )
- /*++
- Routine Description:
- This routine prints C-state statistics for the given processor.
- Arguments:
- Data - Supplies a pointer to the processor data.
- Return Value:
- None.
- --*/
- {
- PSTR Ending;
- ULONGLONG ExitLatency;
- ULONGLONG Frequency;
- ULONG Index;
- PSTR Name;
- PPM_IDLE_STATE State;
- PPM_CSTATE_STATISTICS Statistics;
- ULONGLONG TargetResidency;
- ULONGLONG TimeSpent;
- ULONGLONG TotalEvents;
- Frequency = HlQueryTimeCounterFrequency();
- RtlDebugPrint("Processor %d C-States:\n"
- " Name Exit Target, Count Time\n",
- Data->Driver.ProcessorNumber);
- TotalEvents = 0;
- for (Index = 0; Index <= Data->Driver.StateCount; Index += 1) {
- if (Index == 0) {
- Name = "(halt)";
- ExitLatency = 0;
- TargetResidency = 0;
- } else {
- State = &(Data->Driver.States[Index - 1]);
- Name = State->Name;
- TargetResidency = (State->TargetResidency *
- MICROSECONDS_PER_SECOND) / Frequency;
- ExitLatency = (State->ExitLatency *
- MICROSECONDS_PER_SECOND) / Frequency;
- }
- Statistics = &(Data->Statistics[Index]);
- TimeSpent = (Statistics->TimeSpent * MICROSECONDS_PER_SECOND) /
- Frequency;
- Ending = "us";
- if (TimeSpent > 10 * MICROSECONDS_PER_SECOND) {
- TimeSpent = Statistics->TimeSpent / Frequency;
- Ending = "s";
- }
- RtlDebugPrint("%8s: %5I64d %6I64d, %8I64d %I64d %s\n",
- Name,
- ExitLatency,
- TargetResidency,
- Statistics->EntryCount,
- TimeSpent,
- Ending);
- TotalEvents += Statistics->EntryCount;
- }
- RtlDebugPrint("Total Events: %I64d\n", TotalEvents);
- return;
- }
|