xref: /reactos/win32ss/user/ntuser/power.c (revision 181b666f)
1 /*
2  * PROJECT:         ReactOS Win32k subsystem
3  * LICENSE:         GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:         Power management of the Win32 kernel-mode subsystem
5  * COPYRIGHT:       Copyright 2024 George Bișoc <george.bisoc@reactos.org>
6  */
7 
8 /* INCLUDES ******************************************************************/
9 
10 #include <win32k.h>
11 DBG_DEFAULT_CHANNEL(UserPowerManager);
12 
13 /* GLOBALS *******************************************************************/
14 
15 LIST_ENTRY gPowerCalloutsQueueList;
16 PFAST_MUTEX gpPowerCalloutMutexLock;
17 PKEVENT gpPowerRequestCalloutEvent;
18 PKTHREAD gpPowerCalloutMutexOwnerThread;
19 
20 /* PRIVATE FUNCTIONS *********************************************************/
21 
22 /**
23  * @brief
24  * Handles a power event as a result from an incoming power
25  * callout from the kernel power manager.
26  *
27  * @param[in] pParameters
28  * A pointer to the power event parameters containing the
29  * power event type and sub-code serving as additional datum
30  * for that power event type.
31  */
32 static
33 VOID
IntHandlePowerEventWorker(_In_ PWIN32_POWEREVENT_PARAMETERS pParameters)34 IntHandlePowerEventWorker(
35     _In_ PWIN32_POWEREVENT_PARAMETERS pParameters)
36 {
37     PSPOWEREVENTTYPE PwrEventType;
38     ULONG Code;
39 
40     /* Cache the power event parameters and handle the power callout */
41     PwrEventType = pParameters->EventNumber;
42     Code = pParameters->Code;
43     switch (PwrEventType)
44     {
45         case PsW32SystemTime:
46         {
47             /*
48              * The power manager of the kernel notified us of an impending
49              * time change, broadcast this notification to all present windows.
50              */
51             UserSendNotifyMessage(HWND_BROADCAST,
52                                   WM_TIMECHANGE,
53                                   0,
54                                   0);
55             break;
56         }
57 
58         default:
59         {
60             TRACE("Power event of type %d is currently UNIMPLEMENTED (code %lu)\n", PwrEventType, Code);
61             break;
62         }
63     }
64 }
65 
66 /**
67  * @brief
68  * Validates the power event parameters that come from
69  * a power callout from the kernel power manager.
70  *
71  * @param[in] pParameters
72  * A pointer to the power event parameters containing the
73  * power event type of which is to be validated against
74  * valid power events.
75  *
76  * @return
77  * Returns STATUS_INVALID_PARAMETER if the captured power
78  * event type is unknown, otherwise STATUS_SUCCESS is returned.
79  */
80 static
81 NTSTATUS
IntValidateWin32PowerParams(_In_ PWIN32_POWEREVENT_PARAMETERS pParameters)82 IntValidateWin32PowerParams(
83     _In_ PWIN32_POWEREVENT_PARAMETERS pParameters)
84 {
85     PSPOWEREVENTTYPE PwrEventType;
86 
87     /* Capture the event number and check if it is within bounds */
88     PwrEventType = pParameters->EventNumber;
89     if (PwrEventType < PsW32FullWake || PwrEventType > PsW32MonitorOff)
90     {
91         TRACE("Unknown event number found -> %d\n", PwrEventType);
92         return STATUS_INVALID_PARAMETER;
93     }
94 
95     return STATUS_SUCCESS;
96 }
97 
98 /**
99  * @brief
100  * Gets the next pending power callout from the global queue
101  * list and returns it to the caller. Note that the returned
102  * power callout is delisted from the list.
103  *
104  * @param[in] pPowerCallout
105  * A pointer to a power callout entry that was previously returned
106  * by the same function. If this parameter is set to NULL the function
107  * will return the first callout entry from the list. Otherwise the
108  * function will return the next callout entry of the current power
109  * callout.
110  *
111  * @return
112  * Returns a pointer to a power callout to the caller. If the list is
113  * empty then it will return NULL.
114  *
115  * @remarks
116  * The caller ASSUMES responsibility to lock down the power callout list
117  * before it begins enumerating the global list!
118  */
119 static
120 PWIN32POWERCALLOUT
IntGetNextPowerCallout(_In_ PWIN32POWERCALLOUT pPowerCallout)121 IntGetNextPowerCallout(
122     _In_ PWIN32POWERCALLOUT pPowerCallout)
123 {
124     PLIST_ENTRY Entry;
125     PWIN32POWERCALLOUT pPowerCalloutEntry = NULL;
126 
127     /* Ensure the current calling thread owns the power callout lock */
128     ASSERT_POWER_CALLOUT_LOCK_ACQUIRED();
129 
130     /* This list is empty, acknowledge the caller */
131     if (IsListEmpty(&gPowerCalloutsQueueList))
132     {
133         return NULL;
134     }
135 
136     /* The caller supplied a NULL argument, give them the first entry */
137     if (!pPowerCallout)
138     {
139         Entry = gPowerCalloutsQueueList.Flink;
140     }
141     else
142     {
143         /* Otherwise give the caller the next power callout entry from the list */
144         Entry = pPowerCallout->Link.Flink;
145     }
146 
147     /* Delist the power callout entry from the list and give it to caller */
148     pPowerCalloutEntry = CONTAINING_RECORD(Entry, WIN32POWERCALLOUT, Link);
149     RemoveEntryList(&pPowerCalloutEntry->Link);
150     return pPowerCalloutEntry;
151 }
152 
153 /**
154  * @brief
155  * Deploys all pending power callouts to appropriate power
156  * callout workers.
157  */
158 static
159 VOID
IntDeployPowerCallout(VOID)160 IntDeployPowerCallout(VOID)
161 {
162     PWIN32POWERCALLOUT pWin32PwrCallout;
163 
164     /* Lock the entire USER subsystem down as we do particular stuff */
165     UserEnterExclusive();
166 
167     /*
168      * FIXME: While we did indeed lock the USER subsystem, there is a risk
169      * of the current calling thread might die or hang, so we should probably
170      * lock the thread while we deploy our power callout. The thread info
171      * provides a thread lock field for this purpose (see the ptl member from
172      * the _THREADINFO structure) but ReactOS lacks implementation to handle
173      * this. Suppose a thread happens to get into this fate, the power callout
174      * would never get signaled...
175      */
176 
177     /* Deploy all the pending power callouts to the appropriate callout workers */
178     IntAcquirePowerCalloutLock();
179     for (pWin32PwrCallout = IntGetNextPowerCallout(NULL);
180          pWin32PwrCallout != NULL;
181          pWin32PwrCallout = IntGetNextPowerCallout(pWin32PwrCallout))
182     {
183         if (pWin32PwrCallout->Type == POWER_CALLOUT_EVENT)
184         {
185             IntHandlePowerEventWorker(&pWin32PwrCallout->Params);
186         }
187         else // POWER_CALLOUT_STATE
188         {
189             ERR("Power state callout management is currently not implemented!\n");
190         }
191 
192         /* We are done with this power callout */
193         ExFreePoolWithTag(pWin32PwrCallout, USERTAG_POWER);
194     }
195 
196     /* Release what we locked */
197     IntReleasePowerCalloutLock();
198     UserLeave();
199 }
200 
201 /**
202  * @brief
203  * Enlists a newly allocated power callout into the queue list
204  * for later processing.
205  *
206  * @param[in] pPowerCallout
207  * A pointer to a power callout that is to be inserted into the
208  * queue list.
209  */
210 static
211 VOID
IntEnlistPowerCallout(_In_ PWIN32POWERCALLOUT pPowerCallout)212 IntEnlistPowerCallout(
213     _In_ PWIN32POWERCALLOUT pPowerCallout)
214 {
215     PETHREAD CurrentThread;
216 
217     /* Enlist it to the queue already */
218     IntAcquirePowerCalloutLock();
219     InsertTailList(&gPowerCalloutsQueueList, &pPowerCallout->Link);
220     IntReleasePowerCalloutLock();
221 
222     /*
223      * We have to let CSRSS process this power callout if one of the
224      * following conditions is TRUE for the current calling thread:
225      *
226      * - The process of the calling thread is a system process;
227      * - The process of the calling thread is attached;
228      * - The current calling thread is not a Win32 thread.
229      *
230      * For the second point, we cannot process the power callout ourselves
231      * as we must lock down USER exclusively for our own purpose, which requires
232      * us to be in a critical section. So we do not want to fiddle with a process
233      * that is attached with others.
234      */
235     CurrentThread = PsGetCurrentThread();
236     if (PsIsSystemThread(CurrentThread) ||
237         KeIsAttachedProcess() ||
238         !IntIsThreadWin32Thread(CurrentThread))
239     {
240         /* Alert CSRSS of the presence of an enqueued power callout */
241         KeSetEvent(gpPowerRequestCalloutEvent, EVENT_INCREMENT, FALSE);
242         return;
243     }
244 
245     /* Handle this power callout ourselves */
246     IntDeployPowerCallout();
247 }
248 
249 /* PUBLIC FUNCTIONS **********************************************************/
250 
251 /**
252  * @brief
253  * Initializes the power management side of Win32 kernel-mode
254  * subsystem component. This enables communication between
255  * the power manager of the NT kernel and Win32k.
256  *
257  * @param[in] hPowerRequestEvent
258  * A handle to the global power request event, provided by the
259  * Winsrv module. This allows CSRSS to be notified of power callouts
260  * that cannot be handled by Win32k.
261  *
262  * @return
263  * Returns STATUS_INSUFFICIENT_RESOURCES if pool allocation for the
264  * power callout lock has failed due to lack of necessary memory.
265  * A failure NTSTATUS code is returned if the power request event
266  * could not be referenced. Otherwise STATUS_SUCCESS is returned to
267  * indicate the power management has initialized successfully.
268  */
269 NTSTATUS
270 NTAPI
IntInitWin32PowerManagement(_In_ HANDLE hPowerRequestEvent)271 IntInitWin32PowerManagement(
272     _In_ HANDLE hPowerRequestEvent)
273 {
274     NTSTATUS Status;
275 
276     /* Allocate memory pool for the power callout mutex */
277     gpPowerCalloutMutexLock = ExAllocatePoolZero(NonPagedPool,
278                                                  sizeof(FAST_MUTEX),
279                                                  USERTAG_POWER);
280     if (gpPowerCalloutMutexLock == NULL)
281     {
282         ERR("Failed to allocate pool of memory for the power callout mutex\n");
283         return STATUS_INSUFFICIENT_RESOURCES;
284     }
285 
286     /* Initialize the mutex and owner thread */
287     ExInitializeFastMutex(gpPowerCalloutMutexLock);
288     gpPowerCalloutMutexOwnerThread = NULL;
289 
290     /* Initialize the global queue list and the power callout (aka request) event object */
291     InitializeListHead(&gPowerCalloutsQueueList);
292     Status = ObReferenceObjectByHandle(hPowerRequestEvent,
293                                        EVENT_ALL_ACCESS,
294                                        *ExEventObjectType,
295                                        KernelMode,
296                                        (PVOID *)&gpPowerRequestCalloutEvent,
297                                        NULL);
298     if (!NT_SUCCESS(Status))
299     {
300         ERR("Failed to reference the power callout event handle (Status 0x%08lx)\n", Status);
301         ExFreePoolWithTag(gpPowerCalloutMutexLock, USERTAG_POWER);
302         return Status;
303     }
304 
305     return STATUS_SUCCESS;
306 }
307 
308 /**
309  * @brief
310  * Cleanup procedure that frees all the allocated resources by
311  * the power manager. It is triggered during Win32k subsystem unloading.
312  */
313 NTSTATUS
314 NTAPI
IntWin32PowerManagementCleanup(VOID)315 IntWin32PowerManagementCleanup(VOID)
316 {
317     PWIN32POWERCALLOUT pWin32PwrCallout;
318 
319     /* Dereference the power request event */
320     ObDereferenceObject(gpPowerRequestCalloutEvent);
321     gpPowerRequestCalloutEvent = NULL;
322 
323     /*
324      * Enumerate all pending power callouts and free them. We do not
325      * need to do this with the lock held as the CSR process is tore
326      * apart during Win32k cleanup, so future power callouts would not
327      * be allowed anyway, therefore we are safe.
328      */
329     for (pWin32PwrCallout = IntGetNextPowerCallout(NULL);
330          pWin32PwrCallout != NULL;
331          pWin32PwrCallout = IntGetNextPowerCallout(pWin32PwrCallout))
332     {
333         ExFreePoolWithTag(pWin32PwrCallout, USERTAG_POWER);
334     }
335 
336     /* Tear apart the power callout lock mutex */
337     ExFreePoolWithTag(gpPowerCalloutMutexLock, USERTAG_POWER);
338     gpPowerCalloutMutexLock = NULL;
339     return STATUS_SUCCESS;
340 }
341 
342 /**
343  * @brief
344  * Handles an incoming power event callout from the NT power
345  * manager.
346  *
347  * @param[in] pWin32PwrEventParams
348  * A pointer to power event parameters that is given by the
349  * NT power manager of the kernel.
350  *
351  * @return
352  * Returns STATUS_UNSUCCESSFUL if the Client/Server subsystem is
353  * not running, of which power callouts cannot be handled.
354  * Returns STATUS_INVALID_PARAMETER if the provided power event
355  * parameters are not valid. Returns STATUS_INSUFFICIENT_RESOURCES
356  * if there is a lack of memory to allocate for the power callout.
357  * Otherwise it returns STATUS_SUCCESS to indicate the power callout
358  * was handled successfully.
359  */
360 NTSTATUS
361 NTAPI
IntHandlePowerEvent(_In_ PWIN32_POWEREVENT_PARAMETERS pWin32PwrEventParams)362 IntHandlePowerEvent(
363     _In_ PWIN32_POWEREVENT_PARAMETERS pWin32PwrEventParams)
364 {
365     PWIN32POWERCALLOUT pWin32PwrCallout;
366     NTSTATUS Status;
367 
368     /*
369      * CSRSS is not running. As a consequence, the USER subsystem is neither
370      * up and running as the Client/Server subsystem is responsible to fire
371      * up other subsystems. Another case is that the system undergoes shutdown
372      * and Win32k cleanup is currently in effect. Either way, just quit.
373      */
374     if (!gpepCSRSS)
375     {
376         TRACE("CSRSS is not running, bailing out\n");
377         return STATUS_UNSUCCESSFUL;
378     }
379 
380     /* Validate the power event parameters, just to be sure we have not gotten anything else */
381     Status = IntValidateWin32PowerParams(pWin32PwrEventParams);
382     if (!NT_SUCCESS(Status))
383     {
384         ERR("Could not deploy power callout, invalid Win32 power parameters!\n");
385         return Status;
386     }
387 
388     /* Allocate pool of memory for this power callout */
389     pWin32PwrCallout = ExAllocatePoolZero(NonPagedPool,
390                                           sizeof(WIN32POWERCALLOUT),
391                                           USERTAG_POWER);
392     if (pWin32PwrCallout == NULL)
393     {
394         ERR("Allocating memory for Win32 power callout failed\n");
395         return STATUS_INSUFFICIENT_RESOURCES;
396     }
397 
398     /* Fill the necessary power datum */
399     pWin32PwrCallout->Type = POWER_CALLOUT_EVENT;
400     pWin32PwrCallout->Params.EventNumber = pWin32PwrEventParams->EventNumber;
401     pWin32PwrCallout->Params.Code = pWin32PwrEventParams->Code;
402 
403     /* Enqueue this power request for later processing */
404     IntEnlistPowerCallout(pWin32PwrCallout);
405     return STATUS_SUCCESS;
406 }
407 
408 /**
409  * @brief
410  * Handles an incoming power state callout from the NT power
411  * manager.
412  *
413  * @param[in] pWin32PwrStateParams
414  * A pointer to power state parameters that is given by the
415  * NT power manager of the kernel.
416  */
417 NTSTATUS
418 NTAPI
IntHandlePowerState(_In_ PWIN32_POWERSTATE_PARAMETERS pWin32PwrStateParams)419 IntHandlePowerState(
420     _In_ PWIN32_POWERSTATE_PARAMETERS pWin32PwrStateParams)
421 {
422     /* FIXME */
423     ERR("IntHandlePowerState is UNIMPLEMENTED\n");
424     return STATUS_NOT_IMPLEMENTED;
425 }
426 
427 /* EOF */
428