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 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 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 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 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 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 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 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 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 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