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
FxEventQueue(__in UCHAR QueueDepth)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
Initialize(__in PFX_DRIVER_GLOBALS DriverGlobals)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
Configure(__in FxPkgPnp * Pnp,__in PFN_PNP_EVENT_WORKER WorkerRoutine,__in PVOID Context)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
SetFinished(__in FxCREvent * Event)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
SetDelayedDeletion(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
QueueToThreadWorker(VOID)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
EventQueueWorker(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
FxWorkItemEventQueue(__in UCHAR QueueDepth)308 FxWorkItemEventQueue::FxWorkItemEventQueue(
309 __in UCHAR QueueDepth
310 ) : FxEventQueue(QueueDepth)
311 {
312 }
313
~FxWorkItemEventQueue()314 FxWorkItemEventQueue::~FxWorkItemEventQueue()
315 {
316 m_WorkItem.Free();
317 }
318
319 _Must_inspect_result_
320 NTSTATUS
Init(__inout FxPkgPnp * Pnp,__in PFN_PNP_EVENT_WORKER WorkerRoutine,__in PVOID WorkerContext)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
FxThreadedEventQueue(__in UCHAR QueueDepth)352 FxThreadedEventQueue::FxThreadedEventQueue(
353 __in UCHAR QueueDepth
354 ) : FxEventQueue(QueueDepth)
355 {
356 ExInitializeWorkItem(&m_EventWorkQueueItem,
357 (PWORKER_THREAD_ROUTINE) _WorkerThreadRoutine,
358 this);
359 }
360
~FxThreadedEventQueue(VOID)361 FxThreadedEventQueue::~FxThreadedEventQueue(
362 VOID
363 )
364 {
365 m_WorkItem.Free();
366 }
367
368 _Must_inspect_result_
369 NTSTATUS
Init(__inout FxPkgPnp * Pnp,__in PFN_PNP_EVENT_WORKER WorkerRoutine,__in PVOID WorkerContext)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
_WorkerThreadRoutine(__in PVOID Context)390 FxThreadedEventQueue::_WorkerThreadRoutine(
391 __in PVOID Context
392 )
393 {
394 FxThreadedEventQueue* This = (FxThreadedEventQueue *)Context;
395
396 This->EventQueueWorker();
397 }
398
399 VOID
QueueWorkItem(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
_WorkItemCallback(__in MdDeviceObject DeviceObject,__in PVOID Context)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