windows-nt/Source/XPSP1/NT/base/ntos/po/pwork.c
2020-09-26 16:20:57 +08:00

846 lines
18 KiB
C

/*++
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
--*/
{
ULONG Mask;
KIRQL OldIrql;
//
// 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) 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;
ULONG 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;
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.
//
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;
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
--*/
{
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
--*/
{
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
--*/
{
PoPrint(PO_NOTIFY, ("PopNotify: ProcessorPolicyChanges\n"));
Arg = Arg;
}
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
--*/
{
BOOLEAN IssueCallout;
PO_SET_STATE_FAILURE Failure;
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;
}
PopReleasePolicyLock (FALSE);
if (!NT_SUCCESS(Failure.Status)) {
PoPrint(PO_NOTIFY, ("PopNotify: set state failed (code %x)\n", Failure.Status));
PopEventCalloutDispatch (PsW32SetStateFailed, (ULONG_PTR) &Failure);
}
}