mirror of
https://github.com/reactos/reactos.git
synced 2025-06-05 09:20:30 +00:00
438 lines
9.8 KiB
C++
438 lines
9.8 KiB
C++
/*++
|
|
|
|
Copyright (c) Microsoft. All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
EventQueue.cpp
|
|
|
|
Abstract:
|
|
|
|
This module implements a baseline event queue structure which takes care of
|
|
90% of the work requireed to run a state machine
|
|
|
|
Author:
|
|
|
|
|
|
|
|
Environment:
|
|
|
|
Both kernel and user mode
|
|
|
|
Revision History:
|
|
|
|
|
|
|
|
--*/
|
|
|
|
#include "pnppriv.hpp"
|
|
|
|
extern "C" {
|
|
#if defined(EVENT_TRACING)
|
|
#include "EventQueue.tmh"
|
|
#endif
|
|
}
|
|
|
|
FxEventQueue::FxEventQueue(
|
|
__in UCHAR QueueDepth
|
|
)
|
|
{
|
|
m_PkgPnp = NULL;
|
|
m_EventWorker = NULL;
|
|
|
|
m_HistoryIndex = 0;
|
|
m_QueueHead = 0;
|
|
m_QueueTail = 0;
|
|
m_QueueDepth = QueueDepth;
|
|
|
|
m_WorkItemFinished = NULL;
|
|
m_QueueFlags = 0x0;
|
|
m_WorkItemRunningCount = 0x0;
|
|
}
|
|
|
|
_Must_inspect_result_
|
|
NTSTATUS
|
|
FxEventQueue::Initialize(
|
|
__in PFX_DRIVER_GLOBALS DriverGlobals
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
//
|
|
// For KM, lock initialize always succeeds. For UM, it might fail.
|
|
//
|
|
status = m_StateMachineLock.Initialize();
|
|
if (!NT_SUCCESS(status)) {
|
|
DoTraceLevelMessage(DriverGlobals,
|
|
TRACE_LEVEL_ERROR, TRACINGPNP,
|
|
"Initializing state machine lock failed for EventQueue 0x%p, "
|
|
"status %!STATUS!",
|
|
this, status);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
VOID
|
|
FxEventQueue::Configure(
|
|
__in FxPkgPnp* Pnp,
|
|
__in PFN_PNP_EVENT_WORKER WorkerRoutine,
|
|
__in PVOID Context
|
|
)
|
|
{
|
|
m_PkgPnp = Pnp;
|
|
m_EventWorker = WorkerRoutine;
|
|
m_EventWorkerContext = Context;
|
|
|
|
return;
|
|
}
|
|
|
|
BOOLEAN
|
|
FxEventQueue::SetFinished(
|
|
__in FxCREvent* Event
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Puts the queue into a closed state. If the queue cannot be closed and
|
|
finished in this context, the Event is stored and set when it moves into
|
|
the finished state
|
|
|
|
Arguments:
|
|
Event - the event to set when we move into the finished state
|
|
|
|
Return Value:
|
|
TRUE if the queue is closed in this context, FALSE if the Event should be
|
|
waited on.
|
|
|
|
--*/
|
|
{
|
|
KIRQL irql;
|
|
BOOLEAN result;
|
|
|
|
result = TRUE;
|
|
|
|
Lock(&irql);
|
|
ASSERT((m_QueueFlags & FxEventQueueFlagClosed) == 0x0);
|
|
m_QueueFlags |= FxEventQueueFlagClosed;
|
|
|
|
result = IsIdleLocked();
|
|
|
|
if (result == FALSE) {
|
|
m_WorkItemFinished = Event;
|
|
}
|
|
|
|
Unlock(irql);
|
|
|
|
if (result) {
|
|
Event->Set();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
VOID
|
|
FxEventQueue::SetDelayedDeletion(
|
|
VOID
|
|
)
|
|
{
|
|
KIRQL irql;
|
|
|
|
DoTraceLevelMessage(
|
|
m_PkgPnp->GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
|
"WDFDEVICE 0x%p !devobj 0x%p delaying deletion to outside state machine",
|
|
m_PkgPnp->GetDevice()->GetHandle(),
|
|
m_PkgPnp->GetDevice()->GetDeviceObject());
|
|
|
|
Lock(&irql);
|
|
ASSERT((m_QueueFlags & FxEventQueueFlagDelayDeletion) == 0x0);
|
|
m_QueueFlags |= FxEventQueueFlagDelayDeletion;
|
|
Unlock(irql);
|
|
}
|
|
|
|
BOOLEAN
|
|
FxEventQueue::QueueToThreadWorker(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Generic worker function which encapsulates the logic of whether to enqueue
|
|
onto a different thread if the thread has not already been queued to.
|
|
|
|
NOTE: this function could have been virtual, or call a virtual worker function
|
|
once we have determined that we need to queue to a thread. But to save
|
|
space on vtable storage (why have one unless you really need one?),
|
|
we rearrange the code so that the derived class calls the worker function
|
|
and this function indicates in its return value what the caller should
|
|
do
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Return Value:
|
|
TRUE if the caller should queue to a thread to do the work
|
|
FALSE if the caller shoudl not queue to a thread b/c it has already been
|
|
queued
|
|
|
|
--*/
|
|
{
|
|
KIRQL irql;
|
|
BOOLEAN result;
|
|
|
|
Lock(&irql);
|
|
|
|
//
|
|
// For one reason or another, we couldn't run the state machine on this
|
|
// thread. So queue a work item to do it.
|
|
//
|
|
if (IsEmpty()) {
|
|
//
|
|
// There is no work to do. This means that the caller inserted the
|
|
// event into the queue, dropped the lock, and then another thread came
|
|
// in and processed the event.
|
|
//
|
|
// This check also helps in the rundown case when the queue is closing
|
|
// and the following happens between 2 thread:
|
|
// #1 #2
|
|
// insert event
|
|
// drop lock
|
|
// process event queue
|
|
// queue goes to empty, so event is set
|
|
// try to queue work item
|
|
//
|
|
result = FALSE;
|
|
|
|
DoTraceLevelMessage(
|
|
m_PkgPnp->GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
|
"WDFDEVICE 0x%p !devobj 0x%p not queueing work item to process "
|
|
"event queue", m_PkgPnp->GetDevice()->GetHandle(),
|
|
m_PkgPnp->GetDevice()->GetDeviceObject());
|
|
}
|
|
else if ((m_QueueFlags & FxEventQueueFlagWorkItemQueued) == 0x00) {
|
|
m_QueueFlags |= FxEventQueueFlagWorkItemQueued;
|
|
result = TRUE;
|
|
}
|
|
else {
|
|
//
|
|
// Somebody is already in the process of enqueuing the work item.
|
|
//
|
|
result = FALSE;
|
|
}
|
|
|
|
Unlock(irql);
|
|
|
|
return result;
|
|
}
|
|
|
|
VOID
|
|
FxEventQueue::EventQueueWorker(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This is the work item that attempts to run the queue state machine on
|
|
the special power thread.
|
|
|
|
|
|
--*/
|
|
{
|
|
FxPostProcessInfo info;
|
|
KIRQL irql;
|
|
FxPkgPnp* pPkgPnp;
|
|
|
|
#if (FX_CORE_MODE==FX_CORE_KERNEL_MODE)
|
|
FX_TRACK_DRIVER(m_PkgPnp->GetDriverGlobals());
|
|
#endif
|
|
|
|
//
|
|
// Cache away m_PkgPnp while we know we still have a valid object. Once
|
|
// we Unlock() after the worker routine, the object could be gone until
|
|
// the worker routine set a flag postponing deletion.
|
|
//
|
|
pPkgPnp = m_PkgPnp;
|
|
|
|
Lock(&irql);
|
|
|
|
ASSERT(m_QueueFlags & FxEventQueueFlagWorkItemQueued);
|
|
|
|
//
|
|
// Clear the queued flag, so that it's clear that the work item can
|
|
// be safely re-enqueued.
|
|
//
|
|
m_QueueFlags &= ~FxEventQueueFlagWorkItemQueued;
|
|
|
|
//
|
|
// We should only see this count rise to a small number (like 10 or so).
|
|
//
|
|
ASSERT(m_WorkItemRunningCount < 0xFF);
|
|
m_WorkItemRunningCount++;
|
|
|
|
Unlock(irql);
|
|
|
|
//
|
|
// Call the function that will actually run the state machine.
|
|
//
|
|
m_EventWorker(m_PkgPnp, &info, m_EventWorkerContext);
|
|
|
|
Lock(&irql);
|
|
m_WorkItemRunningCount--;
|
|
GetFinishedState(&info);
|
|
Unlock(irql);
|
|
|
|
//
|
|
// NOTE: There is no need to use a reference count to keep this event queue
|
|
// (and the containing state machine) alive. Instead, the thread
|
|
// which wants to delete the state machine must wait for this work
|
|
// item to exit. If there was a reference to release, we would have
|
|
// a race between Unlock()ing and releasing the reference if the state
|
|
// machine moved into the finished state and deletes the device after
|
|
// we dropped the lock, but before we released the reference.
|
|
//
|
|
// This is important in that the device deletion can trigger
|
|
// DriverUnload to run before the release executes. DriverUnload
|
|
// frees the IFR buffer. If this potential release logs something to
|
|
// the IFR, you would bugcheck. Since it is impossible to defensively
|
|
// prevent all destructors from logging to the IFR, we can't use a
|
|
// ref count here to keep the queue alive.
|
|
//
|
|
|
|
//
|
|
// If Evaluate needs to use pPkgPnp, then the call to the worker routine
|
|
// above made sure that pPkgPnp has not yet been freed.
|
|
//
|
|
info.Evaluate(pPkgPnp);
|
|
}
|
|
|
|
FxWorkItemEventQueue::FxWorkItemEventQueue(
|
|
__in UCHAR QueueDepth
|
|
) : FxEventQueue(QueueDepth)
|
|
{
|
|
}
|
|
|
|
FxWorkItemEventQueue::~FxWorkItemEventQueue()
|
|
{
|
|
m_WorkItem.Free();
|
|
}
|
|
|
|
_Must_inspect_result_
|
|
NTSTATUS
|
|
FxWorkItemEventQueue::Init(
|
|
__inout FxPkgPnp* Pnp,
|
|
__in PFN_PNP_EVENT_WORKER WorkerRoutine,
|
|
__in PVOID WorkerContext
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
Configure(Pnp, WorkerRoutine, WorkerContext);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
status = m_WorkItem.Allocate(
|
|
(MdDeviceObject)(GetIoMgrObjectForWorkItemAllocation())
|
|
);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
FxThreadedEventQueue::FxThreadedEventQueue(
|
|
__in UCHAR QueueDepth
|
|
) : FxEventQueue(QueueDepth)
|
|
{
|
|
ExInitializeWorkItem(&m_EventWorkQueueItem,
|
|
(PWORKER_THREAD_ROUTINE) _WorkerThreadRoutine,
|
|
this);
|
|
}
|
|
|
|
FxThreadedEventQueue::~FxThreadedEventQueue(
|
|
VOID
|
|
)
|
|
{
|
|
m_WorkItem.Free();
|
|
}
|
|
|
|
_Must_inspect_result_
|
|
NTSTATUS
|
|
FxThreadedEventQueue::Init(
|
|
__inout FxPkgPnp* Pnp,
|
|
__in PFN_PNP_EVENT_WORKER WorkerRoutine,
|
|
__in PVOID WorkerContext
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
Configure(Pnp, WorkerRoutine, WorkerContext);
|
|
|
|
|
|
status = m_WorkItem.Allocate(Pnp->GetDevice()->GetDeviceObject());
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
FxThreadedEventQueue::_WorkerThreadRoutine(
|
|
__in PVOID Context
|
|
)
|
|
{
|
|
FxThreadedEventQueue* This = (FxThreadedEventQueue *)Context;
|
|
|
|
This->EventQueueWorker();
|
|
}
|
|
|
|
VOID
|
|
FxThreadedEventQueue::QueueWorkItem(
|
|
VOID
|
|
)
|
|
{
|
|
if (m_PkgPnp->HasPowerThread()) {
|
|
//
|
|
// Use the power thread for the stack
|
|
//
|
|
m_PkgPnp->QueueToPowerThread(&m_EventWorkQueueItem);
|
|
}
|
|
else {
|
|
//
|
|
// Use the work item since the power thread is not available
|
|
//
|
|
m_WorkItem.Enqueue(_WorkItemCallback,
|
|
(FxEventQueue*) this);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
FxThreadedEventQueue::_WorkItemCallback(
|
|
__in MdDeviceObject DeviceObject,
|
|
__in PVOID Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This is the work item that attempts to run the machine on a thread
|
|
separate from the one the caller was using.
|
|
|
|
--*/
|
|
{
|
|
FxThreadedEventQueue* This = (FxThreadedEventQueue *)Context;
|
|
|
|
UNREFERENCED_PARAMETER(DeviceObject);
|
|
|
|
This->EventQueueWorker();
|
|
}
|
|
|