12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625 |
- /*++
- Copyright (c) 2015 Minoca Corp. All Rights Reserved
- Module Name:
- power.c
- Abstract:
- This module implements generic support for device runtime power managment
- within the kernel.
- Author:
- Evan Green 2-Sep-2015
- Environment:
- Kernel
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- #include <minoca/kernel/kernel.h>
- #include "pmp.h"
- #include "iop.h"
- //
- // ---------------------------------------------------------------- Definitions
- //
- //
- // Define the default delay in seconds before a device that has no power
- // references is sent an idle request.
- //
- #define PM_INITIAL_IDLE_DELAY_SECONDS 1
- //
- // Define the number of data points of device idle history to keep, expressed
- // as a bit shift.
- //
- #define PM_DEVICE_HISTORY_SIZE_SHIFT 5
- //
- // ------------------------------------------------------ Data Type Definitions
- //
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- KSTATUS
- PmpInitializeDevice (
- PDEVICE Device
- );
- VOID
- PmpDeviceIdleTimerDpc (
- PDPC Dpc
- );
- VOID
- PmpDeviceIdleWorker (
- PVOID Parameter
- );
- VOID
- PmpDeviceIncrementActiveChildren (
- PDEVICE Device
- );
- VOID
- PmpDeviceDecrementActiveChildren (
- PDEVICE Device
- );
- KSTATUS
- PmpDeviceAddReference (
- PDEVICE Device,
- DEVICE_POWER_REQUEST Request
- );
- KSTATUS
- PmpDeviceQueuePowerTransition (
- PDEVICE Device,
- DEVICE_POWER_REQUEST Request
- );
- KSTATUS
- PmpDeviceResume (
- PDEVICE Device,
- DEVICE_POWER_REQUEST Request
- );
- VOID
- PmpDeviceIdle (
- PDEVICE Device
- );
- VOID
- PmpDeviceSuspend (
- PDEVICE Device
- );
- //
- // -------------------------------------------------------------------- Globals
- //
- //
- // Set this boolean to TRUE to print power transitions.
- //
- BOOL PmDebugPowerTransitions;
- //
- // ------------------------------------------------------------------ Functions
- //
- KERNEL_API
- KSTATUS
- PmInitialize (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine initializes power management infrastructure for a given
- device.
- Arguments:
- Device - Supplies a pointer to the device to prepare to do power management
- calls on.
- Return Value:
- Status code.
- --*/
- {
- if (Device->Power != NULL) {
- return STATUS_SUCCESS;
- }
- return PmpInitializeDevice(Device);
- }
- KERNEL_API
- KSTATUS
- PmDeviceAddReference (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine adds a power management reference on the given device,
- and waits for the device to transition to the active state.
- Arguments:
- Device - Supplies a pointer to the device to add a power reference to.
- Return Value:
- Status code. On failure, the caller will not have a reference on the
- device, and should not assume that the device or its parent lineage is
- active.
- --*/
- {
- return PmpDeviceAddReference(Device, DevicePowerRequestResume);
- }
- KERNEL_API
- KSTATUS
- PmDeviceAddReferenceAsynchronous (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine adds a power management reference on the given device,
- preventing the device from idling until the reference is released.
- Arguments:
- Device - Supplies a pointer to the device to add a power reference to.
- Return Value:
- Status code indicating if the request was successfully queued. On failure,
- the caller will not have the reference on the device.
- --*/
- {
- UINTN PreviousCount;
- PDEVICE_POWER State;
- KSTATUS Status;
- State = Device->Power;
- PreviousCount = RtlAtomicAdd(&(State->ReferenceCount), 1);
- ASSERT(PreviousCount < 0x10000000);
- if (PreviousCount != 0) {
- return STATUS_SUCCESS;
- }
- Status = PmpDeviceQueuePowerTransition(Device, DevicePowerRequestResume);
- if (!KSUCCESS(Status)) {
- RtlAtomicAdd(&(State->ReferenceCount), -1);
- }
- return Status;
- }
- KERNEL_API
- KSTATUS
- PmDeviceReleaseReference (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine releases a power management reference on a device.
- Arguments:
- Device - Supplies a pointer to the device to subtract a power reference
- from.
- Return Value:
- Status code indicating if the idle timer was successfully queued. The
- reference itself is always dropped, even on failure.
- --*/
- {
- UINTN PreviousCount;
- PDEVICE_POWER State;
- KSTATUS Status;
- ULONG TimerQueued;
- State = Device->Power;
- Status = STATUS_SUCCESS;
- PreviousCount = RtlAtomicAdd(&(State->ReferenceCount), -1);
- ASSERT((PreviousCount != 0) && (PreviousCount < 0x10000000));
- if (PreviousCount > 1) {
- return Status;
- }
- //
- // Bump up the idle deadline even if the timer is already queued. The
- // timer will see this and requeue itself.
- //
- State->IdleTimeout = HlQueryTimeCounter() + State->IdleDelay;
- //
- // Try to win the race to queue the timer.
- //
- TimerQueued = RtlAtomicCompareExchange32(&(State->TimerQueued), 1, 0);
- if (TimerQueued == 0) {
- Status = KeQueueTimer(State->IdleTimer,
- TimerQueueSoftWake,
- State->IdleTimeout,
- 0,
- 0,
- State->IdleTimerDpc);
- if (!KSUCCESS(Status)) {
- RtlDebugPrint("PM: Cannot queue idle timer: device %x: %x\n",
- Device,
- Status);
- }
- }
- return Status;
- }
- KERNEL_API
- KSTATUS
- PmDeviceSetState (
- PDEVICE Device,
- DEVICE_POWER_STATE PowerState
- )
- /*++
- Routine Description:
- This routine sets a new power state for the device. This can be used to
- clear an error. It should not be called from a power IRP.
- Arguments:
- Device - Supplies a pointer to the device to set the power state for.
- PowerState - Supplies the new power management state to set. The only
- valid states to set are active and suspended.
- Return Value:
- Status code.
- --*/
- {
- PDEVICE_POWER State;
- KSTATUS Status;
- State = Device->Power;
- Status = STATUS_SUCCESS;
- switch (PowerState) {
- case DevicePowerStateActive:
- if (State->State == DevicePowerStateActive) {
- Status = STATUS_SUCCESS;
- } else {
- //
- // Add a reference on the device to bring it up, then release that
- // reference to send it down towards sleepytown.
- //
- Status = PmpDeviceAddReference(Device,
- DevicePowerRequestMarkActive);
- if (KSUCCESS(Status)) {
- Status = PmDeviceReleaseReference(Device);
- }
- }
- break;
- case DevicePowerStateSuspended:
- KeAcquireQueuedLock(State->Lock);
- if (State->State == DevicePowerStateRemoved) {
- Status = STATUS_DEVICE_NOT_CONNECTED;
- } else {
- if ((State->State == DevicePowerStateActive) ||
- ((State->State == DevicePowerStateTransitioning) &&
- (State->PreviousState == DevicePowerStateActive))) {
- PmpDeviceDecrementActiveChildren(Device->ParentDevice);
- }
- State->State = DevicePowerStateSuspended;
- State->Request = DevicePowerRequestNone;
- }
- KeReleaseQueuedLock(State->Lock);
- break;
- case DevicePowerStateIdle:
- default:
- ASSERT(FALSE);
- Status = STATUS_INVALID_PARAMETER;
- break;
- }
- return Status;
- }
- KSTATUS
- PmInitializeLibrary (
- VOID
- )
- /*++
- Routine Description:
- This routine performs global initialization for the power management
- library. It is called towards the end of I/O initialization.
- Arguments:
- None.
- Return Value:
- None.
- --*/
- {
- KSTATUS Status;
- Status = PmpArchInitialize();
- if (!KSUCCESS(Status)) {
- goto InitializeLibraryEnd;
- }
- InitializeLibraryEnd:
- return Status;
- }
- VOID
- PmpRemoveDevice (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine is called when a device is removed from the system. It cleans
- up the power management state. It is assumed that the device lock is
- already held exclusive.
- Arguments:
- Device - Supplies a pointer to the device to remove.
- Return Value:
- None.
- --*/
- {
- DEVICE_POWER_STATE OldPreviousState;
- DEVICE_POWER_STATE OldState;
- PDEVICE_POWER State;
- State = Device->Power;
- if (State == NULL) {
- return;
- }
- //
- // A transition to the removed state is effective immediately, but must be
- // synchronized with all other transitions.
- //
- KeAcquireQueuedLock(State->Lock);
- OldState = State->State;
- OldPreviousState = State->PreviousState;
- State->State = DevicePowerStateRemoved;
- State->Request = DevicePowerRequestNone;
- RtlAtomicExchange32(&(State->TimerQueued), 1);
- KeCancelTimer(State->IdleTimer);
- KeCancelDpc(State->IdleTimerDpc);
- KeCancelWorkItem(State->IdleTimerWorkItem);
- if (OldState != DevicePowerStateTransitioning) {
- State->PreviousState = OldState;
- }
- KeReleaseQueuedLock(State->Lock);
- //
- // If an active child was just removed, decrement the parent's count.
- //
- if ((OldState == DevicePowerStateActive) ||
- ((OldState == DevicePowerStateTransitioning) &&
- (OldPreviousState == DevicePowerStateActive))) {
- PmpDeviceDecrementActiveChildren(Device->ParentDevice);
- }
- return;
- }
- VOID
- PmpDestroyDevice (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine tears down the power management structures associated with a
- device.
- Arguments:
- Device - Supplies a pointer to the device to tear down.
- Return Value:
- None.
- --*/
- {
- PDEVICE_POWER State;
- if (Device->Power != NULL) {
- State = Device->Power;
- //
- // Work through the timer, DPC, work item flow, starting at the source
- // and squeezing the tube along the way.
- //
- if (State->IdleTimer != NULL) {
- KeDestroyTimer(State->IdleTimer);
- }
- if (State->IdleTimerDpc != NULL) {
- KeDestroyDpc(State->IdleTimerDpc);
- }
- if (State->IdleTimerWorkItem != NULL) {
- KeCancelWorkItem(State->IdleTimerWorkItem);
- KeFlushWorkItem(State->IdleTimerWorkItem);
- KeDestroyWorkItem(State->IdleTimerWorkItem);
- }
- if (State->ActiveEvent != NULL) {
- KeDestroyEvent(State->ActiveEvent);
- }
- if (State->Lock != NULL) {
- KeDestroyQueuedLock(State->Lock);
- }
- if (State->Irp != NULL) {
- IoDestroyIrp(State->Irp);
- }
- if (State->History != NULL) {
- PmpDestroyIdleHistory(State->History);
- }
- Device->Power = NULL;
- MmFreeNonPagedPool(State);
- }
- return;
- }
- VOID
- PmpDevicePowerTransition (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine is called by the worker thread to perform a device power
- transition.
- Arguments:
- Device - Supplies a pointer to the device to work on.
- Return Value:
- None.
- --*/
- {
- PDEVICE_POWER State;
- State = Device->Power;
- if (State->State == DevicePowerStateTransitioning) {
- switch (State->Request) {
- case DevicePowerRequestIdle:
- PmpDeviceIdle(Device);
- break;
- case DevicePowerRequestSuspend:
- PmpDeviceSuspend(Device);
- break;
- //
- // It is OK to do a second unprotected read of the state's request.
- // When a resume or activate request is set, no other request can trump
- // it, not even another resume or active (as only one thread grabs the
- // first reference on the device and starts the resume process).
- //
- case DevicePowerRequestResume:
- case DevicePowerRequestMarkActive:
- PmpDeviceResume(Device, State->Request);
- break;
- default:
- break;
- }
- }
- return;
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
- KSTATUS
- PmpInitializeDevice (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine initializes the power management portion of a device structure.
- Arguments:
- Device - Supplies a pointer to the device to initialize.
- Return Value:
- Status code.
- --*/
- {
- PDEVICE_POWER Power;
- KSTATUS Status;
- Power = MmAllocateNonPagedPool(sizeof(DEVICE_POWER),
- PM_DEVICE_ALLOCATION_TAG);
- if (Power == NULL) {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- }
- RtlZeroMemory(Power, sizeof(DEVICE_POWER));
- Device->Power = Power;
- Power->State = DevicePowerStateSuspended;
- Power->IdleDelay = HlQueryTimeCounterFrequency() *
- PM_INITIAL_IDLE_DELAY_SECONDS;
- Power->Lock = KeCreateQueuedLock();
- Power->ActiveEvent = KeCreateEvent(NULL);
- Power->IdleTimer = KeCreateTimer(PM_DEVICE_ALLOCATION_TAG);
- //
- // This work item should go on the same work queue as the device worker
- // thread to avoid an extra context switch.
- //
- Power->IdleTimerWorkItem = KeCreateWorkItem(IoDeviceWorkQueue,
- WorkPriorityNormal,
- PmpDeviceIdleWorker,
- Device,
- PM_DEVICE_ALLOCATION_TAG);
- Power->IdleTimerDpc = KeCreateDpc(PmpDeviceIdleTimerDpc,
- Power->IdleTimerWorkItem);
- Power->Irp = IoCreateIrp(Device, IrpMajorStateChange, 0);
- Power->History = PmpCreateIdleHistory(IDLE_HISTORY_NON_PAGED,
- PM_DEVICE_HISTORY_SIZE_SHIFT);
- if ((Power->Lock == NULL) ||
- (Power->ActiveEvent == NULL) ||
- (Power->IdleTimer == NULL) ||
- (Power->IdleTimerDpc == NULL) ||
- (Power->IdleTimerWorkItem == NULL) ||
- (Power->Irp == NULL) ||
- (Power->History == NULL)) {
- Status = STATUS_INSUFFICIENT_RESOURCES;
- goto InitializeDeviceEnd;
- }
- //
- // Start the active event as unsignaled since the device is in the
- // suspended state.
- //
- KeSignalEvent(Power->ActiveEvent, SignalOptionUnsignal);
- Status = STATUS_SUCCESS;
- InitializeDeviceEnd:
- if (!KSUCCESS(Status)) {
- PmpDestroyDevice(Device);
- }
- return Status;
- }
- VOID
- PmpDeviceIdleTimerDpc (
- PDPC Dpc
- )
- /*++
- Routine Description:
- This routine is called at dispatch level when the device's idle timer
- expires.
- Arguments:
- Dpc - Supplies a pointer to the DPC that is running.
- Return Value:
- None.
- --*/
- {
- KSTATUS Status;
- //
- // The user data for the DPC is the work item itself, which is important
- // since the power state structure is paged and cannot be touched here.
- //
- Status = KeQueueWorkItem(Dpc->UserData);
- ASSERT(KSUCCESS(Status));
- return;
- }
- VOID
- PmpDeviceIdleWorker (
- PVOID Parameter
- )
- /*++
- Routine Description:
- This routine implements the work item queued when a device's idle timer
- expires.
- Arguments:
- Parameter - Supplies a context parameter, which in this case is a pointer
- to the device.
- Return Value:
- None.
- --*/
- {
- ULONGLONG CurrentTime;
- PDEVICE Device;
- PDEVICE_POWER State;
- KSTATUS Status;
- ULONGLONG Timeout;
- ULONG TimerQueued;
- Device = Parameter;
- State = Device->Power;
- //
- // The timer gets left on a lot, even if it's no longer needed. If there
- // are references on the device now, just do nothing.
- //
- if (State->ReferenceCount != 0) {
- RtlAtomicExchange32(&(State->TimerQueued), 0);
- //
- // Assuming there are still references, then this is done.
- //
- if (State->ReferenceCount != 0) {
- return;
- //
- // The references went away, but may have lost the race to queue the
- // timer. Try to reclaim the right to queue the timer. If someone else
- // got there first, then the timer is requeued and doesn't need
- // the rest of this routine.
- //
- } else {
- TimerQueued = RtlAtomicCompareExchange32(&(State->TimerQueued),
- 1,
- 0);
- if (TimerQueued == 0) {
- return;
- }
- }
- }
- //
- // If the idle timeout has moved beyond the current time, then re-queue
- // the timer for that new time.
- //
- CurrentTime = HlQueryTimeCounter();
- Timeout = State->IdleTimeout;
- if (CurrentTime < Timeout) {
- Status = KeQueueTimer(State->IdleTimer,
- TimerQueueSoftWake,
- Timeout,
- 0,
- 0,
- State->IdleTimerDpc);
- if (!KSUCCESS(Status)) {
- RtlAtomicExchange32(&(State->TimerQueued), 0);
- }
- //
- // The idle timer really did expire. Reset the timer queued variable, and
- // queue the idle transition.
- //
- } else {
- RtlAtomicExchange32(&(State->TimerQueued), 0);
- Status = PmpDeviceQueuePowerTransition(Device, DevicePowerRequestIdle);
- if (!KSUCCESS(Status)) {
- RtlDebugPrint("PM: Failed to queue idle work: %x %x\n",
- Device,
- Status);
- }
- }
- return;
- }
- VOID
- PmpDeviceDecrementActiveChildren (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine decrements the active child count on a given device.
- Arguments:
- Device - Supplies a pointer to the device to subtract an active child count
- from.
- Return Value:
- None.
- --*/
- {
- UINTN PreviousCount;
- PDEVICE_POWER State;
- State = Device->Power;
- if (State == NULL) {
- return;
- }
- PreviousCount = RtlAtomicAdd(&(State->ActiveChildren), -1);
- ASSERT((PreviousCount != 0) && (PreviousCount < 0x10000000));
- //
- // If this is the first active child, release a power reference on this
- // device.
- //
- if (PreviousCount == 1) {
- PmDeviceReleaseReference(Device);
- }
- return;
- }
- KSTATUS
- PmpDeviceAddReference (
- PDEVICE Device,
- DEVICE_POWER_REQUEST Request
- )
- /*++
- Routine Description:
- This routine asynchronously adds a power management reference on the given
- device.
- Arguments:
- Device - Supplies a pointer to the device to add a power reference to.
- Request - Supplies the request type, which can either be actually becoming
- active or just marking it as such.
- Return Value:
- Status code.
- --*/
- {
- UINTN PreviousCount;
- PDEVICE_POWER State;
- KSTATUS Status;
- Status = STATUS_SUCCESS;
- State = Device->Power;
- PreviousCount = RtlAtomicAdd(&(State->ReferenceCount), 1);
- ASSERT(PreviousCount < 0x10000000);
- //
- // Attempt to the resume the device if this is the first reference.
- //
- if (PreviousCount == 0) {
- Status = PmpDeviceResume(Device, Request);
- if (!KSUCCESS(Status)) {
- RtlAtomicAdd(&(State->ReferenceCount), -1);
- }
- //
- // All subsequent references need to wait for the active event and then
- // check the state to see if the resume succeeded.
- //
- } else {
- KeWaitForEvent(Device->Power->ActiveEvent, FALSE, WAIT_TIME_INDEFINITE);
- if (Device->Power->State != DevicePowerStateActive) {
- RtlAtomicAdd(&(State->ReferenceCount), -1);
- Status = STATUS_NOT_READY;
- }
- }
- return Status;
- }
- KSTATUS
- PmpDeviceQueuePowerTransition (
- PDEVICE Device,
- DEVICE_POWER_REQUEST Request
- )
- /*++
- Routine Description:
- This routine queues a power request on a device.
- Arguments:
- Device - Supplies a pointer to the device to affect.
- Request - Supplies the type of request to queue.
- Return Value:
- Status code.
- --*/
- {
- BOOL QueueRequest;
- PDEVICE_POWER State;
- KSTATUS Status;
- State = Device->Power;
- Status = STATUS_SUCCESS;
- //
- // Do a quick exit for resuming a device that's not idle.
- //
- if ((Request == DevicePowerRequestResume) &&
- (State->State == DevicePowerStateActive)) {
- return STATUS_SUCCESS;
- }
- //
- // If the state is already what it should be and there are no other
- // requests, also exit.
- //
- QueueRequest = FALSE;
- KeAcquireQueuedLock(State->Lock);
- //
- // Don't bother if the same request is already queued.
- //
- if ((State->State != DevicePowerStateRemoved) &&
- ((State->State != DevicePowerStateTransitioning) ||
- (State->Request != Request))) {
- switch (Request) {
- //
- // Resume trumps all other requests.
- //
- case DevicePowerRequestResume:
- case DevicePowerRequestMarkActive:
- if (State->State != DevicePowerStateActive) {
- State->Request = Request;
- QueueRequest = TRUE;
- }
- break;
- //
- // Suspend trumps idle.
- //
- case DevicePowerRequestSuspend:
- if (State->State != DevicePowerStateSuspended) {
- if ((State->Request != DevicePowerRequestResume) &&
- (State->Request != DevicePowerRequestMarkActive)) {
- State->Request = Request;
- QueueRequest = TRUE;
- }
- }
- break;
- //
- // Idle only happens if nothing else is going on.
- //
- case DevicePowerRequestIdle:
- if (State->State != DevicePowerStateIdle) {
- if (State->Request == DevicePowerRequestNone) {
- State->Request = Request;
- QueueRequest = TRUE;
- }
- }
- break;
- default:
- ASSERT(FALSE);
- break;
- }
- }
- //
- // If a request is needed, set the state correctly while the lock is held.
- //
- if (QueueRequest != FALSE) {
- if (State->State != DevicePowerStateTransitioning) {
- State->PreviousState = State->State;
- }
- State->State = DevicePowerStateTransitioning;
- }
- KeReleaseQueuedLock(State->Lock);
- //
- // If needed, actually queue the work request now that the lock is released.
- //
- if (QueueRequest != FALSE) {
- Status = IopQueueDeviceWork(Device,
- DeviceActionPowerTransition,
- NULL,
- 0);
- //
- // If queueing the work fails, then attempt to transition the state
- // back to what it was. There may already be an item on the queue and
- // the request may still run, but there is no guarantee of that. The
- // state must be rolled back.
- //
- if (!KSUCCESS(Status)) {
- KeAcquireQueuedLock(State->Lock);
- //
- // If the request is still set, then roll back the state. If it's
- // not, then there is a subsequent attempt at queueing action that
- // may well succeed.
- //
- if (Request == State->Request) {
- State->State = State->PreviousState;
- }
- KeReleaseQueuedLock(State->Lock);
- //
- // If this is a failed resume action, then signal the event. Other
- // threads may be waiting on the event for the resume to succeed.
- //
- if ((Request == DevicePowerRequestResume) ||
- (Request == DevicePowerRequestMarkActive)) {
- KeSignalEvent(State->ActiveEvent, SignalOptionSignalAll);
- }
- }
- }
- return Status;
- }
- KSTATUS
- PmpDeviceResume (
- PDEVICE Device,
- DEVICE_POWER_REQUEST Request
- )
- /*++
- Routine Description:
- This routine performs the actual resume action for a given device.
- Arguments:
- Device - Supplies a pointer to the device to resume.
- Request - Supplies the type of resume to request.
- Return Value:
- Status code.
- --*/
- {
- ULONGLONG CurrentTime;
- ULONGLONG Duration;
- PIRP Irp;
- BOOL LockHeld;
- PDEVICE Parent;
- PDEVICE_POWER ParentState;
- UINTN PreviousCount;
- PDEVICE_POWER State;
- KSTATUS Status;
- ASSERT((Request == DevicePowerRequestResume) ||
- (Request == DevicePowerRequestMarkActive));
- //
- // If the state isn't already active, then the caller won the race to
- // transition it out of an idle or suspended state by being the first to
- // increment the device's reference count. As such, other threads may be
- // waiting on the active event. Except for this case where the device is
- // already active, this routine always needs to release others waiting on
- // the resume transition.
- //
- State = Device->Power;
- if (State->State == DevicePowerStateActive) {
- return STATUS_SUCCESS;
- }
- LockHeld = FALSE;
- CurrentTime = HlQueryTimeCounter();
- //
- // First resume the parent recursively. Always resume the parent, even if
- // the initiali request was to mark the device active. The parent is not
- // necessarily resumed.
- //
- Parent = Device->ParentDevice;
- ParentState = Parent->Power;
- if (ParentState != NULL) {
- PreviousCount = RtlAtomicAdd(&(ParentState->ActiveChildren), 1);
- ASSERT(PreviousCount < 0x10000000);
- //
- // If this is the first active child, up the reference count on the
- // device.
- //
- if (PreviousCount == 0) {
- PreviousCount = RtlAtomicAdd(&(ParentState->ReferenceCount), 1);
- ASSERT(PreviousCount < 0x10000000);
- //
- // If this was the first power reference on this device, resume
- // that device, recursing up parents as needed.
- //
- if (PreviousCount == 0) {
- Status = PmpDeviceResume(Parent, DevicePowerRequestResume);
- if (!KSUCCESS(Status)) {
- goto DeviceResumeEnd;
- }
- }
- }
- //
- // Wait until the parent's state settles. If this thread does not set
- // the active child count to 1 or the reference count to 1, then
- // another thread is doing the work and the device is not resumed until
- // the active event is signaled. Fail the resume transition if the
- // parent doesn't make it into the resumed state.
- //
- KeWaitForEvent(ParentState->ActiveEvent, FALSE, WAIT_TIME_INDEFINITE);
- if (ParentState->State != DevicePowerStateActive) {
- Status = STATUS_NOT_READY;
- goto DeviceResumeEnd;
- }
- }
- //
- // Synchronize the transition to the active state with other requests and
- // work items that might be trying to send the device to idle or suspend.
- //
- KeAcquireQueuedLock(State->Lock);
- LockHeld = TRUE;
- ASSERT(State->State != DevicePowerStateActive);
- if (State->State == DevicePowerStateRemoved) {
- Status = STATUS_DEVICE_NOT_CONNECTED;
- goto DeviceResumeEnd;
- }
- //
- // If the device was transitioning but never made it, then an extra parent
- // reference count was taken (as idle/suspend will return early and not
- // release it).
- //
- if ((State->State == DevicePowerStateTransitioning) &&
- (State->PreviousState == DevicePowerStateActive)) {
- ASSERT((ParentState == NULL) || (ParentState->ActiveChildren > 1));
- PmpDeviceDecrementActiveChildren(Parent);
- }
- if (Request == DevicePowerRequestResume) {
- Irp = State->Irp;
- IoInitializeIrp(Irp);
- Irp->MinorCode = IrpMinorResume;
- Status = IoSendSynchronousIrp(Irp);
- if (KSUCCESS(Status)) {
- Status = IoGetIrpStatus(Irp);
- }
- } else {
- ASSERT(Request == DevicePowerRequestMarkActive);
- Status = STATUS_SUCCESS;
- }
- if (PmDebugPowerTransitions != FALSE) {
- RtlDebugPrint("PM: 0x%08x Active: 0x%08x\n", Device, Status);
- }
- if (KSUCCESS(Status)) {
- //
- // If the device just switched from idle to active, then compute the
- // idle duration.
- //
- if (State->State == DevicePowerStateIdle) {
- ASSERT(State->TransitionTime != 0);
- Duration = CurrentTime - State->TransitionTime;
- PmpIdleHistoryAddDataPoint(State->History, Duration);
- }
- if (State->State != DevicePowerStateTransitioning) {
- State->PreviousState = State->State;
- }
- State->State = DevicePowerStateActive;
- State->TransitionTime = CurrentTime;
- //
- // Smash any outstanding request state. Now that the device is active
- // again, any request associated with a transition is stale.
- //
- State->Request = DevicePowerRequestNone;
- //
- // On failure, the state is either transitioning (with a request), idle, or
- // suspended. Let the device stay idle or suspended and keep any pending
- // transition unless it is a resume transition.
- //
- } else {
- ASSERT((State->State == DevicePowerStateIdle) ||
- (State->State == DevicePowerStateSuspended) ||
- ((State->State == DevicePowerStateTransitioning) &&
- (State->Request != DevicePowerRequestNone)));
- if ((State->State == DevicePowerStateTransitioning) &&
- ((State->Request == DevicePowerRequestResume) ||
- (State->Request == DevicePowerRequestMarkActive))) {
- ASSERT(State->PreviousState != DevicePowerStateTransitioning);
- ASSERT(State->PreviousState != DevicePowerStateActive);
- State->State = State->PreviousState;
- State->Request = DevicePowerRequestNone;
- }
- }
- DeviceResumeEnd:
- if (LockHeld != FALSE) {
- KeReleaseQueuedLock(State->Lock);
- }
- //
- // Signal the event to release any threads waiting on the resume transition.
- // They need to check the state when they wake up in case the resume
- // failed.
- //
- KeSignalEvent(State->ActiveEvent, SignalOptionSignalAll);
- //
- // If it failed, release the references taken on the parent.
- //
- if (!KSUCCESS(Status)) {
- RtlDebugPrint("PM: Failed to resume 0x%08x: 0x%08x\n", Device, Status);
- PmpDeviceDecrementActiveChildren(Parent);
- }
- return Status;
- }
- VOID
- PmpDeviceIdle (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine performs the actual idle action for a given device.
- Arguments:
- Device - Supplies a pointer to the device to resume.
- Return Value:
- None.
- --*/
- {
- ULONGLONG CurrentTime;
- BOOL DecrementParent;
- PIRP Irp;
- ULONG Milliseconds;
- PDEVICE_POWER State;
- KSTATUS Status;
- State = Device->Power;
- //
- // Exit quickly if there are references now, which there often will be. The
- // state should be active or about to become active.
- //
- if (State->ReferenceCount != 0) {
- return;
- }
- DecrementParent = FALSE;
- Status = STATUS_UNSUCCESSFUL;
- KeAcquireQueuedLock(State->Lock);
- if (State->State == DevicePowerStateRemoved) {
- goto DeviceIdleEnd;
- }
- //
- // Unsignal the event now so that there isn't a window in between
- // 1) Checking the references here, and
- // 2) Unsignaling the event
- // where an add reference call could zoom through, get past the event, and
- // then get in trouble when this lumbering idle finally rolls through. It
- // means add reference calls may get stuck briefly while this routine
- // figures out it's not needed.
- //
- KeSignalEvent(State->ActiveEvent, SignalOptionUnsignal);
- //
- // Do nothing if it turns out this request was stale.
- //
- if ((State->State != DevicePowerStateTransitioning) ||
- (State->Request != DevicePowerRequestIdle) ||
- (State->ReferenceCount != 0)) {
- goto DeviceIdleEnd;
- }
- Irp = State->Irp;
- IoInitializeIrp(Irp);
- Irp->MinorCode = IrpMinorIdle;
- Irp->U.Idle.ExpectedDuration = PmpIdleHistoryGetAverage(State->History);
- Status = IoSendSynchronousIrp(Irp);
- if (KSUCCESS(Status)) {
- Status = IoGetIrpStatus(Irp);
- }
- if (PmDebugPowerTransitions != FALSE) {
- Milliseconds = (Irp->U.Idle.ExpectedDuration * 1000ULL) /
- HlQueryTimeCounterFrequency();
- RtlDebugPrint("PM: %x Idle (%d ms): %x\n",
- Device,
- Milliseconds,
- Status);
- }
- ASSERT(State->PreviousState == DevicePowerStateActive);
- if (KSUCCESS(Status)) {
- CurrentTime = HlQueryTimeCounter();
- State->State = DevicePowerStateIdle;
- State->TransitionTime = CurrentTime;
- DecrementParent = TRUE;
- } else {
- State->State = State->PreviousState;
- }
- //
- // Success or failure, this request is old news. No additional idle
- // requests could have been queued while this one was in flight. And this
- // routine bails earlier if the request type is anything other than idle.
- //
- State->Request = DevicePowerRequestNone;
- DeviceIdleEnd:
- //
- // If the device is active because a resume happened before the idle or the
- // idle failed, wake up everything waiting on the active event.
- //
- if (State->State == DevicePowerStateActive) {
- KeSignalEvent(State->ActiveEvent, SignalOptionSignalAll);
- }
- KeReleaseQueuedLock(State->Lock);
- //
- // If the device was put down, then decrement the active child count of
- // the parent. It moved to the idle state from the active state, which held
- // a reference on the parent.
- //
- if (DecrementParent != FALSE) {
- PmpDeviceDecrementActiveChildren(Device->ParentDevice);
- }
- return;
- }
- VOID
- PmpDeviceSuspend (
- PDEVICE Device
- )
- /*++
- Routine Description:
- This routine performs the actual device suspension.
- Arguments:
- Device - Supplies a pointer to the device to resume.
- Return Value:
- None.
- --*/
- {
- BOOL DecrementParent;
- PIRP Irp;
- PDEVICE_POWER State;
- KSTATUS Status;
- DecrementParent = FALSE;
- State = Device->Power;
- KeAcquireQueuedLock(State->Lock);
- if (State->State == DevicePowerStateRemoved) {
- goto DeviceSuspendEnd;
- }
- KeSignalEvent(State->ActiveEvent, SignalOptionUnsignal);
- //
- // Do nothing if it turns out this request was stale.
- //
- if ((State->State != DevicePowerStateTransitioning) ||
- (State->Request != DevicePowerRequestSuspend)) {
- goto DeviceSuspendEnd;
- }
- Irp = State->Irp;
- IoInitializeIrp(Irp);
- Irp->MinorCode = IrpMinorSuspend;
- Status = IoSendSynchronousIrp(Irp);
- if (KSUCCESS(Status)) {
- Status = IoGetIrpStatus(Irp);
- }
- if (PmDebugPowerTransitions != FALSE) {
- RtlDebugPrint("PM: %x Suspend: %x\n", Device, Status);
- }
- ASSERT((State->PreviousState == DevicePowerStateActive) ||
- (State->PreviousState == DevicePowerStateIdle));
- if (KSUCCESS(Status)) {
- State->State = DevicePowerStateSuspended;
- if (State->PreviousState == DevicePowerStateActive) {
- DecrementParent = TRUE;
- }
- } else {
- State->State = State->PreviousState;
- }
- //
- // Success or failure, this request is old news. No additional suspend
- // requests could have been queued while this one was in flight. And this
- // routine bails earlier if the request type is anything other than suspend.
- //
- State->Request = DevicePowerRequestNone;
- DeviceSuspendEnd:
- //
- // If the device is active because a resume happened before the suspend or
- // the suspend failed to transition from active to suspended, wake up
- // everything waiting on the active event.
- //
- if (State->State == DevicePowerStateActive) {
- KeSignalEvent(State->ActiveEvent, SignalOptionSignalAll);
- }
- KeReleaseQueuedLock(State->Lock);
- //
- // If the device was put down, then decrement the active child count of
- // the parent. This only needs to happen if the previous state was the
- // active state. The device may have already been idle, in which case it
- // would not have held a reference on its parent.
- //
- if (DecrementParent != FALSE) {
- PmpDeviceDecrementActiveChildren(Device->ParentDevice);
- }
- return;
- }
|