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