|
|
/*++
Copyright (c) 1990 Microsoft Corporation
Module Name:
pwork.c
Abstract:
Main work dispatcher in the power policy manager
Author:
Ken Reneris (kenr) 17-Jan-1997
Revision History:
--*/
#include "pop.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, PopAcquirePolicyLock)
#pragma alloc_text(PAGE, PopReleasePolicyLock)
#pragma alloc_text(PAGE, PopPolicyWorkerMain)
#pragma alloc_text(PAGE, PopPolicyWorkerNotify)
#pragma alloc_text(PAGE, PopPolicyTimeChange)
#pragma alloc_text(PAGE, PopDispatchCallout)
#pragma alloc_text(PAGE, PopDispatchCallback)
#pragma alloc_text(PAGE, PopDispatchDisplayRequired)
#pragma alloc_text(PAGE, PopDispatchFullWake)
#pragma alloc_text(PAGE, PopDispatchEventCodes)
#pragma alloc_text(PAGE, PopDispatchAcDcCallback)
#pragma alloc_text(PAGE, PopDispatchPolicyCallout)
#pragma alloc_text(PAGE, PopDispatchSetStateFailure)
#endif
VOID PopAcquirePolicyLock( VOID ) { PAGED_CODE();
KeEnterCriticalRegion(); ExAcquireResourceExclusiveLite (&PopPolicyLock, TRUE); //
// Make sure we are not acquiring this recursively
//
ASSERT(PopPolicyLockThread == NULL); PopPolicyLockThread = KeGetCurrentThread(); }
VOID PopReleasePolicyLock( IN BOOLEAN CheckForWork ) { PAGED_CODE();
ASSERT (PopPolicyLockThread == KeGetCurrentThread()); PopPolicyLockThread = NULL; ExReleaseResourceLite(&PopPolicyLock);
//
// If CheckForWork is set, then this thread is about ready
// to leave the policy manager and it may have set a worker
// pending bit.
//
// N.B. the WorkerPending test is not synchronized, but
// since we're only concered with bits the current thread
// may have set that's OK.
//
if (CheckForWork && (PopWorkerPending & PopWorkerStatus)) {
//
// Worker bit is unmasked and pending. Turn this thread
// into a worker.
//
//
// Handle any pending work
//
PopPolicyWorkerThread (NULL); }
KeLeaveCriticalRegion (); }
VOID PopGetPolicyWorker ( IN ULONG WorkerType ) /*++
Routine Description:
This function enqueus a worker thread for the particular WorkerType. At a maximum one worker thread per type may be dispatched, and typically fewer threads are actually dispatched as any given worker thread will call the new highest priority non-busy dispatch function until all pending work is completed before existing.
Arguments:
WorkerType - Which worker to enqueue for dispatching
Return Value:
None
--*/ { KIRQL OldIrql;
KeAcquireSpinLock (&PopWorkerSpinLock, &OldIrql);
//
// Set pending to get worker to dispatch to handler
//
PopWorkerPending |= WorkerType;
KeReleaseSpinLock (&PopWorkerSpinLock, OldIrql); }
NTSTATUS PopCompletePolicyIrp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) /*++
Routine Description:
This function handles the completion of a policy manager IRP. Policy manager IRPs have a stack location containing the irp handler function to dispatch too. In this function the irp is queue to the irp complete queue and a main worker is allocated if needed to run the queue.
Arguments:
DeviceObject -
Irp - The irp which has completed
Context -
Return Value:
STATUS_MORE_PROCESSING_REQUIRED
--*/ { KIRQL OldIrql;
UNREFERENCED_PARAMETER (DeviceObject); UNREFERENCED_PARAMETER (Context);
//
// Put the irp on a queue for a worker thread
//
KeAcquireSpinLock (&PopWorkerSpinLock, &OldIrql); InsertTailList (&PopPolicyIrpQueue, &Irp->Tail.Overlay.ListEntry);
//
// Wait until base drivers are loaded before dispatching any policy irps
//
if (PopDispatchPolicyIrps) {
//
// Set pending to get worker to dispatch to handler
//
PopWorkerPending |= PO_WORKER_MAIN;
//
// If worker is not already running queue a thread
//
if ((PopWorkerStatus & (PO_WORKER_MAIN | PO_WORKER_STATUS)) == (PO_WORKER_MAIN | PO_WORKER_STATUS) ) {
PopWorkerStatus &= ~PO_WORKER_STATUS; ExQueueWorkItem (&PopPolicyWorker, DelayedWorkQueue);
}
}
//
// If this irp has been cancelled, then make sure to clear the cancel flag
//
if (Irp->IoStatus.Status == STATUS_CANCELLED) {
Irp->Cancel = FALSE;
} KeReleaseSpinLock (&PopWorkerSpinLock, OldIrql);
return STATUS_MORE_PROCESSING_REQUIRED; }
VOID PopCheckForWork ( IN BOOLEAN GetWorker ) /*++
Routine Description:
Checks for outstanding work and dispatches a worker if needed.
Arguments:
None
Return Value:
None
--*/ { KIRQL Irql;
//
// If pending work, handle it
//
if (PopWorkerPending & PopWorkerStatus) {
//
// If current thread already owns the policy lock,
// then just return - we will handle the work when the
// lock is released
//
if (PopPolicyLockThread == KeGetCurrentThread()) { return ; }
//
// Handle the work
//
Irql = KeGetCurrentIrql(); if (!GetWorker && Irql < DISPATCH_LEVEL) {
//
// Use calling thread
//
KeEnterCriticalRegion (); PopPolicyWorkerThread (NULL); KeLeaveCriticalRegion ();
} else {
//
// Get worker thread to handle it
//
KeAcquireSpinLock (&PopWorkerSpinLock, &Irql); if (PopWorkerStatus & PO_WORKER_STATUS) { PopWorkerStatus &= ~PO_WORKER_STATUS; ExQueueWorkItem (&PopPolicyWorker, DelayedWorkQueue); } KeReleaseSpinLock (&PopWorkerSpinLock, Irql);
} } }
VOID PopPolicyWorkerThread ( PVOID Context ) /*++
Routine Description:
Main policy manager worker thread dispatcher. Sends the worker thread to the highest pending priority handler which does not already have a worker thread. Loops until no handler can be dispatched too.
Arguments:
Return Value:
None
--*/ { ULONG WorkerType; ULONG Mask; KIRQL OldIrql; ULONG i; ULONG DelayedWork;
PAGED_CODE();
try { //
// Dispatch
//
KeAcquireSpinLock (&PopWorkerSpinLock, &OldIrql); PopWorkerStatus |= (ULONG) ((ULONG_PTR)Context);
DelayedWork = 0; while (WorkerType = (PopWorkerPending & PopWorkerStatus)) {
//
// Get highest priority worker
//
i = KeFindFirstSetRightMember(WorkerType); Mask = 1 << i;
//
// Clear pending and indicate busy status
//
PopWorkerPending &= ~Mask; PopWorkerStatus &= ~Mask; KeReleaseSpinLock (&PopWorkerSpinLock, OldIrql);
//
// Dispatch to handler
//
DelayedWork |= PopWorkerTypes[i] ();
//
// No longer in progress
//
KeAcquireSpinLock (&PopWorkerSpinLock, &OldIrql); PopWorkerStatus |= Mask; }
PopWorkerPending |= DelayedWork; KeReleaseSpinLock (&PopWorkerSpinLock, OldIrql);
} except (PopExceptionFilter(GetExceptionInformation(), FALSE)) {
} }
ULONG PopPolicyWorkerMain ( VOID ) /*++
Routine Description:
Main policy worker thread. Dispatches any completed policy manager irps.
Arguments:
None
Return Value:
None
--*/ { IN PIRP Irp; PIO_STACK_LOCATION IrpSp; POP_IRP_HANDLER IrpHandler; PLIST_ENTRY Entry;
PopAcquirePolicyLock ();
//
// Dispatch any policy irps which have completed
//
while (Entry = ExInterlockedRemoveHeadList (&PopPolicyIrpQueue, &PopWorkerSpinLock)) { Irp = CONTAINING_RECORD (Entry, IRP, Tail.Overlay.ListEntry); IrpSp = IoGetCurrentIrpStackLocation(Irp);
//
// Dispatch irp to handler
//
IrpHandler = (POP_IRP_HANDLER) (ULONG_PTR) IrpSp->Parameters.Others.Argument3; IrpHandler ((PDEVICE_OBJECT) IrpSp->Parameters.Others.Argument1, Irp, (PVOID) IrpSp->Parameters.Others.Argument2); }
PopReleasePolicyLock (FALSE); PopCheckForWork (TRUE);
return 0; }
VOID PopEventCalloutDispatch ( IN PSPOWEREVENTTYPE EventNumber, IN ULONG_PTR Code ) { WIN32_POWEREVENT_PARAMETERS Parms; ULONG Console; PVOID OpaqueSession; KAPC_STATE ApcState; NTSTATUS Status;
Parms.EventNumber = EventNumber; Parms.Code = Code;
ASSERT(MmIsSessionAddress((PVOID)PopEventCallout));
if (EventNumber == PsW32GdiOn || EventNumber == PsW32GdiOff) { //
// These events go to the console session only.
// The ActiveConsoleId session is stored with the SharedUserData.
//
Console = SharedUserData->ActiveConsoleId;
//
// Unfortunately, it is not guaranteed to be valid during a console
// session change, and there is no way to know when that is happening,
// so if it's not valid, just default to session 0, which is always
// there.
//
if (Console == ((ULONG)-1)) { Console = 0; }
if ((PsGetCurrentProcess()->Flags & PS_PROCESS_FLAGS_IN_SESSION) && (Console == PsGetCurrentProcessSessionId())) { //
// If the caller is already in the specified session, call directly.
//
PopEventCallout(&Parms);
} else { //
// Attach to the console session and dispatch the event.
//
OpaqueSession = MmGetSessionById(Console); if (OpaqueSession) {
Status = MmAttachSession(OpaqueSession, &ApcState); ASSERT(NT_SUCCESS(Status));
if (NT_SUCCESS(Status)) {
PopEventCallout(&Parms);
Status = MmDetachSession(OpaqueSession, &ApcState); ASSERT(NT_SUCCESS(Status)); }
Status = MmQuitNextSession(OpaqueSession); ASSERT(NT_SUCCESS(Status)); } }
} else { //
// All other events are broadcast to all sessions.
//
for (OpaqueSession = MmGetNextSession(NULL); OpaqueSession != NULL; OpaqueSession = MmGetNextSession(OpaqueSession)) {
if ((PsGetCurrentProcess()->Flags & PS_PROCESS_FLAGS_IN_SESSION) && (MmGetSessionId(OpaqueSession) == PsGetCurrentProcessSessionId())) { //
// If the caller is already in the specified session, call directly.
//
PopEventCallout(&Parms);
} else { //
// Attach to the session and dispatch the event.
//
Status = MmAttachSession(OpaqueSession, &ApcState); ASSERT(NT_SUCCESS(Status));
if (NT_SUCCESS(Status)) {
PopEventCallout(&Parms);
Status = MmDetachSession(OpaqueSession, &ApcState); ASSERT(NT_SUCCESS(Status)); } } } } return; }
ULONG PopPolicyTimeChange ( VOID ) { PopEventCalloutDispatch (PsW32SystemTime, 0); return 0; }
VOID PopSetNotificationWork ( IN ULONG Flags ) /*++
Routine Description:
Sets notification flags for the USER notification worker thread. Each bit is a different type of outstanding notification that is to be processed.
Arguments:
Flags - The notifications to set
Return Value:
None
--*/ { //
// Are the flags set
//
if ((PopNotifyEvents & Flags) != Flags) { PoPrint(PO_NOTIFY, ("PopSetNotificationWork: Queue notify of: %x\n", Flags)); InterlockedOr (&PopNotifyEvents, Flags); PopGetPolicyWorker (PO_WORKER_NOTIFY); } }
ULONG PopPolicyWorkerNotify ( VOID ) /*++
Routine Description:
USER notification worker. Processes each set bit in NotifyEvents.
Arguments:
None
Return Value:
None
--*/ { ULONG i; LONG Flags; ULONG Mask; const POP_NOTIFY_WORK* NotifyWork;
//
// If Win32 event callout is registered, then don't dispatch right now
//
if (!PopEventCallout) { return PO_WORKER_NOTIFY; }
//
// While events are pending collect them and dispatch them
//
while (Flags = InterlockedExchange (&PopNotifyEvents, 0)) {
while (Flags) {
//
// Get change
//
i = KeFindFirstSetRightMember(Flags); Mask = 1 << i; Flags &= ~Mask; NotifyWork = PopNotifyWork + i;
//
// Dispatch it
//
NotifyWork->Function (NotifyWork->Arg); } } return 0; }
VOID PopDispatchCallout ( IN ULONG Arg ) { PopEventCalloutDispatch (Arg, 0); }
VOID PopDispatchCallback ( IN ULONG Arg ) { // Sundown: Arg is zero-extended
ExNotifyCallback (ExCbPowerState, ULongToPtr(Arg), 0); }
VOID PopDispatchDisplayRequired ( IN ULONG Arg ) /*++
Routine Description:
Notify user32 of the current "display required" setting. Zero, means the display may timeout. Non-zero, means the display is in use until told otherwise.
--*/ { ULONG i;
UNREFERENCED_PARAMETER (Arg);
i = PopAttributes[POP_DISPLAY_ATTRIBUTE].Count; PoPrint(PO_NOTIFY, ("PopNotify: DisplayRequired %x\n", i));
//
// If the display is in use but has not yet been turned on, then do so now
//
if (((PopFullWake & (PO_GDI_STATUS | PO_GDI_ON_PENDING)) == PO_GDI_ON_PENDING)) {
PoPrint(PO_PACT, ("PopEventDispatch: gdi on\n")); InterlockedOr (&PopFullWake, PO_GDI_STATUS); PopEventCalloutDispatch (PsW32GdiOn, 0); } PopEventCalloutDispatch (PsW32DisplayState, i); }
VOID PopDispatchFullWake ( IN ULONG Arg ) /*++
Routine Description:
Notify user32 that the system has fully awoken. Also reset the idle detection to the current policy
--*/ {
//
// If we're not in the middle setting the system state, then check the pending
// flags.
//
UNREFERENCED_PARAMETER (Arg);
if (PopAction.State != PO_ACT_SET_SYSTEM_STATE) {
//
// Notify user32 of the wake events
//
if ((PopFullWake & (PO_GDI_STATUS | PO_GDI_ON_PENDING)) == PO_GDI_ON_PENDING) { PoPrint(PO_PACT, ("PopEventDispatch: gdi on\n")); InterlockedOr (&PopFullWake, PO_GDI_STATUS); PopEventCalloutDispatch (PsW32GdiOn, 0); }
if ((PopFullWake & (PO_FULL_WAKE_STATUS | PO_FULL_WAKE_PENDING)) == PO_FULL_WAKE_PENDING) { PoPrint(PO_PACT, ("PopEventDispatch: full wake\n")); InterlockedOr (&PopFullWake, PO_FULL_WAKE_STATUS); PopEventCalloutDispatch (PsW32FullWake, 0);
//
// Reset the idle detection policy
//
PopAcquirePolicyLock(); PopInitSIdle (); PopReleasePolicyLock (FALSE); } } }
VOID PopDispatchEventCodes ( IN ULONG Arg ) /*++
Routine Description:
Notify user32 of the queued event codes.
--*/ { ULONG i; ULONG Code;
UNREFERENCED_PARAMETER (Arg);
PopAcquirePolicyLock(); for (i=0; i < POP_MAX_EVENT_CODES; i++) { if (PopEventCode[i]) { Code = PopEventCode[i]; PopEventCode[i] = 0; PopReleasePolicyLock (FALSE);
PoPrint(PO_NOTIFY, ("PopNotify: Event %x\n", Code)); PopEventCalloutDispatch (PsW32EventCode, Code);
PopAcquirePolicyLock (); } }
PopResetSwitchTriggers(); PopReleasePolicyLock(FALSE); }
VOID PopDispatchAcDcCallback ( IN ULONG Arg ) /*++
Routine Description:
Notify the system callback of the current policy as either being AC or DC
--*/ { UNREFERENCED_PARAMETER (Arg);
ExNotifyCallback ( ExCbPowerState, UIntToPtr(PO_CB_AC_STATUS), UIntToPtr((PopPolicy == &PopAcPolicy)) ); }
VOID PopDispatchPolicyCallout ( IN ULONG Arg ) /*++
Routine Description:
Notify user32 that the active policy has changed
--*/ { UNREFERENCED_PARAMETER (Arg);
PoPrint(PO_NOTIFY, ("PopNotify: PolicyChanged\n")); PopEventCalloutDispatch (PsW32PowerPolicyChanged, PopPolicy->VideoTimeout); }
VOID PopDispatchProcessorPolicyCallout ( IN ULONG Arg ) /*++
Routine Description:
Not used right now. But required so that we don't have a NULL entry in the PopNotifyWork array
--*/ { UNREFERENCED_PARAMETER (Arg);
PoPrint(PO_NOTIFY, ("PopNotify: ProcessorPolicyChanges\n")); }
VOID PopDispatchSetStateFailure ( IN ULONG Arg ) /*++
Routine Description:
Notify user32 that there was a failure during an async system state operation. E.g., no error code was returned to anyone, yet the operation failed
--*/ { PO_SET_STATE_FAILURE Failure;
UNREFERENCED_PARAMETER (Arg);
RtlZeroMemory (&Failure, sizeof(Failure));
PopAcquirePolicyLock();
//
// If the action state is idle, check to see if we should notify
// win32 of the failure
//
if (PopAction.State == PO_ACT_IDLE && !NT_SUCCESS(PopAction.Status) && (PopAction.Flags & (POWER_ACTION_UI_ALLOWED | POWER_ACTION_CRITICAL)) ) {
Failure.Status = PopAction.Status; Failure.PowerAction = PopAction.Action; Failure.MinState = PopAction.LightestState; Failure.Flags = PopAction.Flags; }
//
// Reset PopAction to the default. Otherwise, any power requests following
// this failure will refer to PopAction, which contains data from this
// failed request.
//
PopResetActionDefaults();
PopReleasePolicyLock (FALSE);
if (!NT_SUCCESS(Failure.Status)) { PoPrint(PO_NOTIFY, ("PopNotify: set state failed (code %x)\n", Failure.Status)); PopEventCalloutDispatch (PsW32SetStateFailed, (ULONG_PTR) &Failure); } }
|