diff --git a/win32ss/CMakeLists.txt b/win32ss/CMakeLists.txt index ebf0084dcad..6555fb19eca 100644 --- a/win32ss/CMakeLists.txt +++ b/win32ss/CMakeLists.txt @@ -147,6 +147,7 @@ list(APPEND SOURCE user/ntuser/ntstubs.c user/ntuser/ntuser.c user/ntuser/painting.c + user/ntuser/power.c user/ntuser/prop.c user/ntuser/scrollbar.c user/ntuser/scrollex.c diff --git a/win32ss/user/ntuser/power.c b/win32ss/user/ntuser/power.c new file mode 100644 index 00000000000..5b3319fbd1c --- /dev/null +++ b/win32ss/user/ntuser/power.c @@ -0,0 +1,427 @@ +/* + * PROJECT: ReactOS Win32k subsystem + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Power management of the Win32 kernel-mode subsystem + * COPYRIGHT: Copyright 2024 George Bișoc + */ + +/* INCLUDES ******************************************************************/ + +#include +DBG_DEFAULT_CHANNEL(UserPowerManager); + +/* GLOBALS *******************************************************************/ + +LIST_ENTRY gPowerCalloutsQueueList; +PFAST_MUTEX gpPowerCalloutMutexLock; +PKEVENT gpPowerRequestCalloutEvent; +PKTHREAD gpPowerCalloutMutexOwnerThread; + +/* PRIVATE FUNCTIONS *********************************************************/ + +/** + * @brief + * Handles a power event as a result from an incoming power + * callout from the kernel power manager. + * + * @param[in] pParameters + * A pointer to the power event parameters containing the + * power event type and sub-code serving as additional datum + * for that power event type. + */ +static +VOID +IntHandlePowerEventWorker( + _In_ PWIN32_POWEREVENT_PARAMETERS pParameters) +{ + PSPOWEREVENTTYPE PwrEventType; + ULONG Code; + + /* Cache the power event parameters and handle the power callout */ + PwrEventType = pParameters->EventNumber; + Code = pParameters->Code; + switch (PwrEventType) + { + case PsW32SystemTime: + { + /* + * The power manager of the kernel notified us of an impending + * time change, broadcast this notification to all present windows. + */ + UserSendNotifyMessage(HWND_BROADCAST, + WM_TIMECHANGE, + 0, + 0); + break; + } + + default: + { + TRACE("Power event of type %d is currently UNIMPLEMENTED (code %lu)\n", PwrEventType, Code); + break; + } + } +} + +/** + * @brief + * Validates the power event parameters that come from + * a power callout from the kernel power manager. + * + * @param[in] pParameters + * A pointer to the power event parameters containing the + * power event type of which is to be validated against + * valid power events. + * + * @return + * Returns STATUS_INVALID_PARAMETER if the captured power + * event type is unknown, otherwise STATUS_SUCCESS is returned. + */ +static +NTSTATUS +IntValidateWin32PowerParams( + _In_ PWIN32_POWEREVENT_PARAMETERS pParameters) +{ + PSPOWEREVENTTYPE PwrEventType; + + /* Capture the event number and check if it is within bounds */ + PwrEventType = pParameters->EventNumber; + if (PwrEventType < PsW32FullWake || PwrEventType > PsW32MonitorOff) + { + TRACE("Unknown event number found -> %d\n", PwrEventType); + return STATUS_INVALID_PARAMETER; + } + + return STATUS_SUCCESS; +} + +/** + * @brief + * Gets the next pending power callout from the global queue + * list and returns it to the caller. Note that the returned + * power callout is delisted from the list. + * + * @param[in] pPowerCallout + * A pointer to a power callout entry that was previously returned + * by the same function. If this parameter is set to NULL the function + * will return the first callout entry from the list. Otherwise the + * function will return the next callout entry of the current power + * callout. + * + * @return + * Returns a pointer to a power callout to the caller. If the list is + * empty then it will return NULL. + * + * @remarks + * The caller ASSUMES responsibility to lock down the power callout list + * before it begins enumerating the global list! + */ +static +PWIN32POWERCALLOUT +IntGetNextPowerCallout( + _In_ PWIN32POWERCALLOUT pPowerCallout) +{ + PLIST_ENTRY Entry; + PWIN32POWERCALLOUT pPowerCalloutEntry = NULL; + + /* Ensure the current calling thread owns the power callout lock */ + ASSERT_POWER_CALLOUT_LOCK_ACQUIRED(); + + /* This list is empty, acknowledge the caller */ + if (IsListEmpty(&gPowerCalloutsQueueList)) + { + return NULL; + } + + /* The caller supplied a NULL argument, give them the first entry */ + if (!pPowerCallout) + { + Entry = gPowerCalloutsQueueList.Flink; + } + else + { + /* Otherwise give the caller the next power callout entry from the list */ + Entry = pPowerCallout->Link.Flink; + } + + /* Delist the power callout entry from the list and give it to caller */ + pPowerCalloutEntry = CONTAINING_RECORD(Entry, WIN32POWERCALLOUT, Link); + RemoveEntryList(&pPowerCalloutEntry->Link); + return pPowerCalloutEntry; +} + +/** + * @brief + * Deploys all pending power callouts to appropriate power + * callout workers. + */ +static +VOID +IntDeployPowerCallout(VOID) +{ + PWIN32POWERCALLOUT pWin32PwrCallout; + + /* Lock the entire USER subsystem down as we do particular stuff */ + UserEnterExclusive(); + + /* + * FIXME: While we did indeed lock the USER subsystem, there is a risk + * of the current calling thread might die or hang, so we should probably + * lock the thread while we deploy our power callout. The thread info + * provides a thread lock field for this purpose (see the ptl member from + * the _THREADINFO structure) but ReactOS lacks implementation to handle + * this. Suppose a thread happens to get into this fate, the power callout + * would never get signaled... + */ + + /* Deploy all the pending power callouts to the appropriate callout workers */ + IntAcquirePowerCalloutLock(); + for (pWin32PwrCallout = IntGetNextPowerCallout(NULL); + pWin32PwrCallout != NULL; + pWin32PwrCallout = IntGetNextPowerCallout(pWin32PwrCallout)) + { + if (pWin32PwrCallout->Type == POWER_CALLOUT_EVENT) + { + IntHandlePowerEventWorker(&pWin32PwrCallout->Params); + } + else // POWER_CALLOUT_STATE + { + ERR("Power state callout management is currently not implemented!\n"); + } + + /* We are done with this power callout */ + ExFreePoolWithTag(pWin32PwrCallout, USERTAG_POWER); + } + + /* Release what we locked */ + IntReleasePowerCalloutLock(); + UserLeave(); +} + +/** + * @brief + * Enlists a newly allocated power callout into the queue list + * for later processing. + * + * @param[in] pPowerCallout + * A pointer to a power callout that is to be inserted into the + * queue list. + */ +static +VOID +IntEnlistPowerCallout( + _In_ PWIN32POWERCALLOUT pPowerCallout) +{ + PETHREAD CurrentThread; + + /* Enlist it to the queue already */ + IntAcquirePowerCalloutLock(); + InsertTailList(&gPowerCalloutsQueueList, &pPowerCallout->Link); + IntReleasePowerCalloutLock(); + + /* + * We have to let CSRSS process this power callout if one of the + * following conditions is TRUE for the current calling thread: + * + * - The process of the calling thread is a system process; + * - The process of the calling thread is attached; + * - The current calling thread is not a Win32 thread. + * + * For the second point, we cannot process the power callout ourselves + * as we must lock down USER exclusively for our own purpose, which requires + * us to be in a critical section. So we do not want to fiddle with a process + * that is attached with others. + */ + CurrentThread = PsGetCurrentThread(); + if (PsIsSystemThread(CurrentThread) || + KeIsAttachedProcess() || + !IntIsThreadWin32Thread(CurrentThread)) + { + /* Alert CSRSS of the presence of an enqueued power callout */ + KeSetEvent(gpPowerRequestCalloutEvent, EVENT_INCREMENT, FALSE); + return; + } + + /* Handle this power callout ourselves */ + IntDeployPowerCallout(); +} + +/* PUBLIC FUNCTIONS **********************************************************/ + +/** + * @brief + * Initializes the power management side of Win32 kernel-mode + * subsystem component. This enables communication between + * the power manager of the NT kernel and Win32k. + * + * @param[in] hPowerRequestEvent + * A handle to the global power request event, provided by the + * Winsrv module. This allows CSRSS to be notified of power callouts + * that cannot be handled by Win32k. + * + * @return + * Returns STATUS_INSUFFICIENT_RESOURCES if pool allocation for the + * power callout lock has failed due to lack of necessary memory. + * A failure NTSTATUS code is returned if the power request event + * could not be referenced. Otherwise STATUS_SUCCESS is returned to + * indicate the power management has initialized successfully. + */ +NTSTATUS +NTAPI +IntInitWin32PowerManagement( + _In_ HANDLE hPowerRequestEvent) +{ + NTSTATUS Status; + + /* Allocate memory pool for the power callout mutex */ + gpPowerCalloutMutexLock = ExAllocatePoolZero(NonPagedPool, + sizeof(FAST_MUTEX), + USERTAG_POWER); + if (gpPowerCalloutMutexLock == NULL) + { + ERR("Failed to allocate pool of memory for the power callout mutex\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Initialize the mutex and owner thread */ + ExInitializeFastMutex(gpPowerCalloutMutexLock); + gpPowerCalloutMutexOwnerThread = NULL; + + /* Initialize the global queue list and the power callout (aka request) event object */ + InitializeListHead(&gPowerCalloutsQueueList); + Status = ObReferenceObjectByHandle(hPowerRequestEvent, + EVENT_ALL_ACCESS, + *ExEventObjectType, + KernelMode, + (PVOID *)&gpPowerRequestCalloutEvent, + NULL); + if (!NT_SUCCESS(Status)) + { + ERR("Failed to reference the power callout event handle (Status 0x%08lx)\n", Status); + ExFreePoolWithTag(gpPowerCalloutMutexLock, USERTAG_POWER); + return Status; + } + + return STATUS_SUCCESS; +} + +/** + * @brief + * Cleanup procedure that frees all the allocated resources by + * the power manager. It is triggered during Win32k subsystem unloading. + */ +NTSTATUS +NTAPI +IntWin32PowerManagementCleanup(VOID) +{ + PWIN32POWERCALLOUT pWin32PwrCallout; + + /* Dereference the power request event */ + ObDereferenceObject(gpPowerRequestCalloutEvent); + gpPowerRequestCalloutEvent = NULL; + + /* + * Enumerate all pending power callouts and free them. We do not + * need to do this with the lock held as the CSR process is tore + * apart during Win32k cleanup, so future power callouts would not + * be allowed anyway, therefore we are safe. + */ + for (pWin32PwrCallout = IntGetNextPowerCallout(NULL); + pWin32PwrCallout != NULL; + pWin32PwrCallout = IntGetNextPowerCallout(pWin32PwrCallout)) + { + ExFreePoolWithTag(pWin32PwrCallout, USERTAG_POWER); + } + + /* Tear apart the power callout lock mutex */ + ExFreePoolWithTag(gpPowerCalloutMutexLock, USERTAG_POWER); + gpPowerCalloutMutexLock = NULL; + return STATUS_SUCCESS; +} + +/** + * @brief + * Handles an incoming power event callout from the NT power + * manager. + * + * @param[in] pWin32PwrEventParams + * A pointer to power event parameters that is given by the + * NT power manager of the kernel. + * + * @return + * Returns STATUS_UNSUCCESSFUL if the Client/Server subsystem is + * not running, of which power callouts cannot be handled. + * Returns STATUS_INVALID_PARAMETER if the provided power event + * parameters are not valid. Returns STATUS_INSUFFICIENT_RESOURCES + * if there is a lack of memory to allocate for the power callout. + * Otherwise it returns STATUS_SUCCESS to indicate the power callout + * was handled successfully. + */ +NTSTATUS +NTAPI +IntHandlePowerEvent( + _In_ PWIN32_POWEREVENT_PARAMETERS pWin32PwrEventParams) +{ + PWIN32POWERCALLOUT pWin32PwrCallout; + NTSTATUS Status; + + /* + * CSRSS is not running. As a consequence, the USER subsystem is neither + * up and running as the Client/Server subsystem is responsible to fire + * up other subsystems. Another case is that the system undergoes shutdown + * and Win32k cleanup is currently in effect. Either way, just quit. + */ + if (!gpepCSRSS) + { + TRACE("CSRSS is not running, bailing out\n"); + return STATUS_UNSUCCESSFUL; + } + + /* Validate the power event parameters, just to be sure we have not gotten anything else */ + Status = IntValidateWin32PowerParams(pWin32PwrEventParams); + if (!NT_SUCCESS(Status)) + { + ERR("Could not deploy power callout, invalid Win32 power parameters!\n"); + return Status; + } + + /* Allocate pool of memory for this power callout */ + pWin32PwrCallout = ExAllocatePoolZero(NonPagedPool, + sizeof(WIN32POWERCALLOUT), + USERTAG_POWER); + if (pWin32PwrCallout == NULL) + { + ERR("Allocating memory for Win32 power callout failed\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Fill the necessary power datum */ + pWin32PwrCallout->Type = POWER_CALLOUT_EVENT; + pWin32PwrCallout->Params.EventNumber = pWin32PwrEventParams->EventNumber; + pWin32PwrCallout->Params.Code = pWin32PwrEventParams->Code; + + /* Enqueue this power request for later processing */ + IntEnlistPowerCallout(pWin32PwrCallout); + return STATUS_SUCCESS; +} + +/** + * @brief + * Handles an incoming power state callout from the NT power + * manager. + * + * @param[in] pWin32PwrStateParams + * A pointer to power state parameters that is given by the + * NT power manager of the kernel. + */ +NTSTATUS +NTAPI +IntHandlePowerState( + _In_ PWIN32_POWERSTATE_PARAMETERS pWin32PwrStateParams) +{ + /* FIXME */ + ERR("IntHandlePowerState is UNIMPLEMENTED\n"); + return STATUS_NOT_IMPLEMENTED; +} + +/* EOF */ diff --git a/win32ss/user/ntuser/power.h b/win32ss/user/ntuser/power.h new file mode 100644 index 00000000000..cf4e651ba80 --- /dev/null +++ b/win32ss/user/ntuser/power.h @@ -0,0 +1,105 @@ +/* + * PROJECT: ReactOS Win32k subsystem + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Power management of the Win32 kernel-mode subsystem + * COPYRIGHT: Copyright 2024 George Bișoc + */ + +#pragma once + +// +// Power Callout type enumeration +// +typedef enum tagPOWER_CALLOUT_TYPE +{ + POWER_CALLOUT_EVENT = 0, + POWER_CALLOUT_STATE +} POWER_CALLOUT_TYPE; + +// +// Power Callout structure +// +typedef struct tagWIN32POWERCALLOUT +{ + /* Links the power callout with the global queue list */ + LIST_ENTRY Link; + + /* + * Set to POWER_CALLOUT_STATE if the upcoming power callout is a power state + * (IntHandlePowerState does that), POWER_CALLOUT_EVENT otherwise + */ + POWER_CALLOUT_TYPE Type; + + /* The captured Win32 power event parameters (the params are set to none if Type == POWER_STATE) */ + WIN32_POWEREVENT_PARAMETERS Params; +} WIN32POWERCALLOUT, *PWIN32POWERCALLOUT; + +// +// Ensures the power callout mutex lock is acquired +// +#define ASSERT_POWER_CALLOUT_LOCK_ACQUIRED() \ + ASSERT(gpPowerCalloutMutexOwnerThread == KeGetCurrentThread()) + +// +// Power Manager data +// +extern LIST_ENTRY gPowerCalloutsQueueList; +extern PFAST_MUTEX gpPowerCalloutMutexLock; +extern PKEVENT gpPowerRequestCalloutEvent; +extern PKTHREAD gpPowerCalloutMutexOwnerThread; + +// +// Function prototypes +// +NTSTATUS +NTAPI +IntInitWin32PowerManagement( + _In_ HANDLE hPowerRequestEvent); + +NTSTATUS +NTAPI +IntWin32PowerManagementCleanup(VOID); + +NTSTATUS +NTAPI +IntHandlePowerEvent( + _In_ PWIN32_POWEREVENT_PARAMETERS pWin32PwrEventParams); + +NTSTATUS +NTAPI +IntHandlePowerState( + _In_ PWIN32_POWERSTATE_PARAMETERS pWin32PwrStateParams); + +// +// Power Callout locking mechanism +// +FORCEINLINE +VOID +IntAcquirePowerCalloutLock(VOID) +{ + KeEnterCriticalRegion(); + ExAcquireFastMutexUnsafe(gpPowerCalloutMutexLock); + gpPowerCalloutMutexOwnerThread = KeGetCurrentThread(); +} + +FORCEINLINE +VOID +IntReleasePowerCalloutLock(VOID) +{ + ExReleaseFastMutexUnsafe(gpPowerCalloutMutexLock); + gpPowerCalloutMutexOwnerThread = NULL; + KeLeaveCriticalRegion(); +} + +// +// Checks if the following thread is a Win32 thread +// +FORCEINLINE +BOOL +IntIsThreadWin32Thread( + _In_ PETHREAD Thread) +{ + return (Thread->Tcb.Win32Thread != NULL) ? TRUE : FALSE; +} + +/* EOF */ diff --git a/win32ss/win32kp.h b/win32ss/win32kp.h index 3f446574ba6..fe547a26610 100644 --- a/win32ss/win32kp.h +++ b/win32ss/win32kp.h @@ -84,6 +84,7 @@ typedef struct _DC *PDC; #include "user/ntuser/window.h" #include "user/ntuser/security.h" #include "user/ntuser/sysparams.h" +#include "user/ntuser/power.h" #include "user/ntuser/prop.h" #include "user/ntuser/guicheck.h" #include "user/ntuser/useratom.h"