1 /*++ 2 3 Copyright (c) Microsoft. All rights reserved. 4 5 Module Name: 6 7 EventQueue.cpp 8 9 Abstract: 10 11 This module implements a baseline event queue structure which takes care of 12 90% of the work requireed to run a state machine 13 14 Author: 15 16 17 18 Environment: 19 20 Both kernel and user mode 21 22 Revision History: 23 24 25 26 --*/ 27 28 #include "pnppriv.hpp" 29 30 extern "C" { 31 #if defined(EVENT_TRACING) 32 #include "EventQueue.tmh" 33 #endif 34 } 35 36 FxEventQueue::FxEventQueue( 37 __in UCHAR QueueDepth 38 ) 39 { 40 m_PkgPnp = NULL; 41 m_EventWorker = NULL; 42 43 m_HistoryIndex = 0; 44 m_QueueHead = 0; 45 m_QueueTail = 0; 46 m_QueueDepth = QueueDepth; 47 48 m_WorkItemFinished = NULL; 49 m_QueueFlags = 0x0; 50 m_WorkItemRunningCount = 0x0; 51 } 52 53 _Must_inspect_result_ 54 NTSTATUS 55 FxEventQueue::Initialize( 56 __in PFX_DRIVER_GLOBALS DriverGlobals 57 ) 58 { 59 NTSTATUS status; 60 61 // 62 // For KM, lock initialize always succeeds. For UM, it might fail. 63 // 64 status = m_StateMachineLock.Initialize(); 65 if (!NT_SUCCESS(status)) { 66 DoTraceLevelMessage(DriverGlobals, 67 TRACE_LEVEL_ERROR, TRACINGPNP, 68 "Initializing state machine lock failed for EventQueue 0x%p, " 69 "status %!STATUS!", 70 this, status); 71 } 72 73 return status; 74 } 75 76 VOID 77 FxEventQueue::Configure( 78 __in FxPkgPnp* Pnp, 79 __in PFN_PNP_EVENT_WORKER WorkerRoutine, 80 __in PVOID Context 81 ) 82 { 83 m_PkgPnp = Pnp; 84 m_EventWorker = WorkerRoutine; 85 m_EventWorkerContext = Context; 86 87 return; 88 } 89 90 BOOLEAN 91 FxEventQueue::SetFinished( 92 __in FxCREvent* Event 93 ) 94 /*++ 95 96 Routine Description: 97 Puts the queue into a closed state. If the queue cannot be closed and 98 finished in this context, the Event is stored and set when it moves into 99 the finished state 100 101 Arguments: 102 Event - the event to set when we move into the finished state 103 104 Return Value: 105 TRUE if the queue is closed in this context, FALSE if the Event should be 106 waited on. 107 108 --*/ 109 { 110 KIRQL irql; 111 BOOLEAN result; 112 113 result = TRUE; 114 115 Lock(&irql); 116 ASSERT((m_QueueFlags & FxEventQueueFlagClosed) == 0x0); 117 m_QueueFlags |= FxEventQueueFlagClosed; 118 119 result = IsIdleLocked(); 120 121 if (result == FALSE) { 122 m_WorkItemFinished = Event; 123 } 124 125 Unlock(irql); 126 127 if (result) { 128 Event->Set(); 129 } 130 131 return result; 132 } 133 134 VOID 135 FxEventQueue::SetDelayedDeletion( 136 VOID 137 ) 138 { 139 KIRQL irql; 140 141 DoTraceLevelMessage( 142 m_PkgPnp->GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP, 143 "WDFDEVICE 0x%p !devobj 0x%p delaying deletion to outside state machine", 144 m_PkgPnp->GetDevice()->GetHandle(), 145 m_PkgPnp->GetDevice()->GetDeviceObject()); 146 147 Lock(&irql); 148 ASSERT((m_QueueFlags & FxEventQueueFlagDelayDeletion) == 0x0); 149 m_QueueFlags |= FxEventQueueFlagDelayDeletion; 150 Unlock(irql); 151 } 152 153 BOOLEAN 154 FxEventQueue::QueueToThreadWorker( 155 VOID 156 ) 157 /*++ 158 159 Routine Description: 160 Generic worker function which encapsulates the logic of whether to enqueue 161 onto a different thread if the thread has not already been queued to. 162 163 NOTE: this function could have been virtual, or call a virtual worker function 164 once we have determined that we need to queue to a thread. But to save 165 space on vtable storage (why have one unless you really need one?), 166 we rearrange the code so that the derived class calls the worker function 167 and this function indicates in its return value what the caller should 168 do 169 170 Arguments: 171 None 172 173 Return Value: 174 TRUE if the caller should queue to a thread to do the work 175 FALSE if the caller shoudl not queue to a thread b/c it has already been 176 queued 177 178 --*/ 179 { 180 KIRQL irql; 181 BOOLEAN result; 182 183 Lock(&irql); 184 185 // 186 // For one reason or another, we couldn't run the state machine on this 187 // thread. So queue a work item to do it. 188 // 189 if (IsEmpty()) { 190 // 191 // There is no work to do. This means that the caller inserted the 192 // event into the queue, dropped the lock, and then another thread came 193 // in and processed the event. 194 // 195 // This check also helps in the rundown case when the queue is closing 196 // and the following happens between 2 thread: 197 // #1 #2 198 // insert event 199 // drop lock 200 // process event queue 201 // queue goes to empty, so event is set 202 // try to queue work item 203 // 204 result = FALSE; 205 206 DoTraceLevelMessage( 207 m_PkgPnp->GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP, 208 "WDFDEVICE 0x%p !devobj 0x%p not queueing work item to process " 209 "event queue", m_PkgPnp->GetDevice()->GetHandle(), 210 m_PkgPnp->GetDevice()->GetDeviceObject()); 211 } 212 else if ((m_QueueFlags & FxEventQueueFlagWorkItemQueued) == 0x00) { 213 m_QueueFlags |= FxEventQueueFlagWorkItemQueued; 214 result = TRUE; 215 } 216 else { 217 // 218 // Somebody is already in the process of enqueuing the work item. 219 // 220 result = FALSE; 221 } 222 223 Unlock(irql); 224 225 return result; 226 } 227 228 VOID 229 FxEventQueue::EventQueueWorker( 230 VOID 231 ) 232 /*++ 233 234 Routine Description: 235 This is the work item that attempts to run the queue state machine on 236 the special power thread. 237 238 239 --*/ 240 { 241 FxPostProcessInfo info; 242 KIRQL irql; 243 FxPkgPnp* pPkgPnp; 244 245 #if (FX_CORE_MODE==FX_CORE_KERNEL_MODE) 246 FX_TRACK_DRIVER(m_PkgPnp->GetDriverGlobals()); 247 #endif 248 249 // 250 // Cache away m_PkgPnp while we know we still have a valid object. Once 251 // we Unlock() after the worker routine, the object could be gone until 252 // the worker routine set a flag postponing deletion. 253 // 254 pPkgPnp = m_PkgPnp; 255 256 Lock(&irql); 257 258 ASSERT(m_QueueFlags & FxEventQueueFlagWorkItemQueued); 259 260 // 261 // Clear the queued flag, so that it's clear that the work item can 262 // be safely re-enqueued. 263 // 264 m_QueueFlags &= ~FxEventQueueFlagWorkItemQueued; 265 266 // 267 // We should only see this count rise to a small number (like 10 or so). 268 // 269 ASSERT(m_WorkItemRunningCount < 0xFF); 270 m_WorkItemRunningCount++; 271 272 Unlock(irql); 273 274 // 275 // Call the function that will actually run the state machine. 276 // 277 m_EventWorker(m_PkgPnp, &info, m_EventWorkerContext); 278 279 Lock(&irql); 280 m_WorkItemRunningCount--; 281 GetFinishedState(&info); 282 Unlock(irql); 283 284 // 285 // NOTE: There is no need to use a reference count to keep this event queue 286 // (and the containing state machine) alive. Instead, the thread 287 // which wants to delete the state machine must wait for this work 288 // item to exit. If there was a reference to release, we would have 289 // a race between Unlock()ing and releasing the reference if the state 290 // machine moved into the finished state and deletes the device after 291 // we dropped the lock, but before we released the reference. 292 // 293 // This is important in that the device deletion can trigger 294 // DriverUnload to run before the release executes. DriverUnload 295 // frees the IFR buffer. If this potential release logs something to 296 // the IFR, you would bugcheck. Since it is impossible to defensively 297 // prevent all destructors from logging to the IFR, we can't use a 298 // ref count here to keep the queue alive. 299 // 300 301 // 302 // If Evaluate needs to use pPkgPnp, then the call to the worker routine 303 // above made sure that pPkgPnp has not yet been freed. 304 // 305 info.Evaluate(pPkgPnp); 306 } 307 308 FxWorkItemEventQueue::FxWorkItemEventQueue( 309 __in UCHAR QueueDepth 310 ) : FxEventQueue(QueueDepth) 311 { 312 } 313 314 FxWorkItemEventQueue::~FxWorkItemEventQueue() 315 { 316 m_WorkItem.Free(); 317 } 318 319 _Must_inspect_result_ 320 NTSTATUS 321 FxWorkItemEventQueue::Init( 322 __inout FxPkgPnp* Pnp, 323 __in PFN_PNP_EVENT_WORKER WorkerRoutine, 324 __in PVOID WorkerContext 325 ) 326 { 327 NTSTATUS status; 328 329 Configure(Pnp, WorkerRoutine, WorkerContext); 330 331 332 333 334 335 336 337 338 339 340 341 status = m_WorkItem.Allocate( 342 (MdDeviceObject)(GetIoMgrObjectForWorkItemAllocation()) 343 ); 344 345 if (!NT_SUCCESS(status)) { 346 return status; 347 } 348 349 return STATUS_SUCCESS; 350 } 351 352 FxThreadedEventQueue::FxThreadedEventQueue( 353 __in UCHAR QueueDepth 354 ) : FxEventQueue(QueueDepth) 355 { 356 ExInitializeWorkItem(&m_EventWorkQueueItem, 357 (PWORKER_THREAD_ROUTINE) _WorkerThreadRoutine, 358 this); 359 } 360 361 FxThreadedEventQueue::~FxThreadedEventQueue( 362 VOID 363 ) 364 { 365 m_WorkItem.Free(); 366 } 367 368 _Must_inspect_result_ 369 NTSTATUS 370 FxThreadedEventQueue::Init( 371 __inout FxPkgPnp* Pnp, 372 __in PFN_PNP_EVENT_WORKER WorkerRoutine, 373 __in PVOID WorkerContext 374 ) 375 { 376 NTSTATUS status; 377 378 Configure(Pnp, WorkerRoutine, WorkerContext); 379 380 381 status = m_WorkItem.Allocate(Pnp->GetDevice()->GetDeviceObject()); 382 if (!NT_SUCCESS(status)) { 383 return status; 384 } 385 386 return STATUS_SUCCESS; 387 } 388 389 VOID 390 FxThreadedEventQueue::_WorkerThreadRoutine( 391 __in PVOID Context 392 ) 393 { 394 FxThreadedEventQueue* This = (FxThreadedEventQueue *)Context; 395 396 This->EventQueueWorker(); 397 } 398 399 VOID 400 FxThreadedEventQueue::QueueWorkItem( 401 VOID 402 ) 403 { 404 if (m_PkgPnp->HasPowerThread()) { 405 // 406 // Use the power thread for the stack 407 // 408 m_PkgPnp->QueueToPowerThread(&m_EventWorkQueueItem); 409 } 410 else { 411 // 412 // Use the work item since the power thread is not available 413 // 414 m_WorkItem.Enqueue(_WorkItemCallback, 415 (FxEventQueue*) this); 416 } 417 } 418 419 VOID 420 FxThreadedEventQueue::_WorkItemCallback( 421 __in MdDeviceObject DeviceObject, 422 __in PVOID Context 423 ) 424 /*++ 425 426 Routine Description: 427 This is the work item that attempts to run the machine on a thread 428 separate from the one the caller was using. 429 430 --*/ 431 { 432 FxThreadedEventQueue* This = (FxThreadedEventQueue *)Context; 433 434 UNREFERENCED_PARAMETER(DeviceObject); 435 436 This->EventQueueWorker(); 437 } 438 439