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