mirror of
https://github.com/reactos/reactos.git
synced 2025-03-10 18:24:02 +00:00
6487 lines
189 KiB
C++
6487 lines
189 KiB
C++
/*++
|
||
|
||
|
||
Copyright (c) Microsoft. All rights reserved.
|
||
|
||
Module Name:
|
||
|
||
FxPkgPnp.cpp
|
||
|
||
Abstract:
|
||
|
||
This module implements the pnp IRP handlers for the driver framework.
|
||
|
||
Author:
|
||
|
||
|
||
|
||
Environment:
|
||
|
||
Both kernel and user mode
|
||
|
||
Revision History:
|
||
|
||
|
||
|
||
|
||
|
||
--*/
|
||
|
||
#include "pnppriv.hpp"
|
||
|
||
#include <initguid.h>
|
||
#include <wdmguid.h>
|
||
|
||
extern "C" {
|
||
|
||
#if defined(EVENT_TRACING)
|
||
#include "FxPkgPnp.tmh"
|
||
#endif
|
||
|
||
}
|
||
|
||
/* dc7a8e51-49b3-4a3a-9e81-625205e7d729 */
|
||
const GUID FxPkgPnp::GUID_POWER_THREAD_INTERFACE = {
|
||
0xdc7a8e51, 0x49b3, 0x4a3a, { 0x9e, 0x81, 0x62, 0x52, 0x05, 0xe7, 0xd7, 0x29 }
|
||
};
|
||
|
||
FxPkgPnp::FxPkgPnp(
|
||
__in PFX_DRIVER_GLOBALS FxDriverGlobals,
|
||
__in CfxDevice* Device,
|
||
__in WDFTYPE Type
|
||
) :
|
||
FxPackage(FxDriverGlobals, Device, Type)
|
||
{
|
||
ULONG i;
|
||
|
||
m_DmaEnablerList = NULL;
|
||
m_RemovalDeviceList = NULL;
|
||
m_UsageDependentDeviceList = NULL;
|
||
|
||
//
|
||
// Initialize the structures to the default state and then override the
|
||
// non WDF std default values to the unsupported / off values.
|
||
//
|
||
m_PnpStateAndCaps.Value =
|
||
FxPnpStateDisabledUseDefault |
|
||
FxPnpStateDontDisplayInUIUseDefault |
|
||
FxPnpStateFailedUseDefault |
|
||
FxPnpStateNotDisableableUseDefault |
|
||
FxPnpStateRemovedUseDefault |
|
||
FxPnpStateResourcesChangedUseDefault |
|
||
|
||
FxPnpCapLockSupportedUseDefault |
|
||
FxPnpCapEjectSupportedUseDefault |
|
||
FxPnpCapRemovableUseDefault |
|
||
FxPnpCapDockDeviceUseDefault |
|
||
FxPnpCapUniqueIDUseDefault |
|
||
FxPnpCapSilentInstallUseDefault |
|
||
FxPnpCapSurpriseRemovalOKUseDefault |
|
||
FxPnpCapHardwareDisabledUseDefault |
|
||
FxPnpCapNoDisplayInUIUseDefault
|
||
;
|
||
|
||
m_PnpCapsAddress = (ULONG) -1;
|
||
m_PnpCapsUINumber = (ULONG) -1;
|
||
|
||
RtlZeroMemory(&m_PowerCaps, sizeof(m_PowerCaps));
|
||
m_PowerCaps.Caps =
|
||
FxPowerCapDeviceD1UseDefault |
|
||
FxPowerCapDeviceD2UseDefault |
|
||
FxPowerCapWakeFromD0UseDefault |
|
||
FxPowerCapWakeFromD1UseDefault |
|
||
FxPowerCapWakeFromD2UseDefault |
|
||
FxPowerCapWakeFromD3UseDefault
|
||
;
|
||
|
||
m_PowerCaps.DeviceWake = PowerDeviceMaximum;
|
||
m_PowerCaps.SystemWake = PowerSystemMaximum;
|
||
|
||
m_PowerCaps.D1Latency = (ULONG) -1;
|
||
m_PowerCaps.D2Latency = (ULONG) -1;
|
||
m_PowerCaps.D3Latency = (ULONG) -1;
|
||
|
||
m_PowerCaps.States = 0;
|
||
for (i = 0; i < PowerSystemMaximum; i++) {
|
||
_SetPowerCapState(i, PowerDeviceMaximum, &m_PowerCaps.States);
|
||
}
|
||
|
||
RtlZeroMemory(&m_D3ColdInterface, sizeof(m_D3ColdInterface));
|
||
RtlZeroMemory(&m_SpecialSupport[0], sizeof(m_SpecialSupport));
|
||
RtlZeroMemory(&m_SpecialFileCount[0], sizeof(m_SpecialFileCount));
|
||
|
||
m_PowerThreadInterface.Interface.Size = sizeof(m_PowerThreadInterface);
|
||
m_PowerThreadInterface.Interface.Version = 1;
|
||
m_PowerThreadInterface.Interface.Context = this;
|
||
m_PowerThreadInterface.Interface.InterfaceReference = &FxPkgPnp::_PowerThreadInterfaceReference;
|
||
m_PowerThreadInterface.Interface.InterfaceDereference = &FxPkgPnp::_PowerThreadInterfaceDereference;
|
||
m_PowerThreadInterface.PowerThreadEnqueue = &FxPkgPnp::_PowerThreadEnqueue;
|
||
m_PowerThread = NULL;
|
||
m_HasPowerThread = FALSE;
|
||
m_PowerThreadInterfaceReferenceCount = 1;
|
||
m_PowerThreadEvent = NULL;
|
||
|
||
m_DeviceStopCount = 0;
|
||
m_CapsQueried = FALSE;
|
||
m_InternalFailure = FALSE;
|
||
m_FailedAction = WdfDeviceFailedUndefined;
|
||
|
||
//
|
||
// We only set the pending child count to 1 once we know we have successfully
|
||
// created an FxDevice and initialized it fully. If we delete an FxDevice
|
||
// which is half baked, it cannot have any FxChildLists which have any
|
||
// pending children on them.
|
||
//
|
||
m_PendingChildCount = 0;
|
||
|
||
m_QueryInterfaceHead.Next = NULL;
|
||
|
||
m_DeviceInterfaceHead.Next = NULL;
|
||
m_DeviceInterfacesCanBeEnabled = FALSE;
|
||
|
||
m_Failed = FALSE;
|
||
m_SetDeviceRemoveProcessed = FALSE;
|
||
|
||
m_SystemPowerState = PowerSystemWorking;
|
||
m_DevicePowerState = WdfPowerDeviceD3Final;
|
||
m_DevicePowerStateOld = WdfPowerDeviceD3Final;
|
||
|
||
m_PendingPnPIrp = NULL;
|
||
m_PendingSystemPowerIrp = NULL;
|
||
m_PendingDevicePowerIrp = NULL;
|
||
m_SystemPowerAction = (UCHAR) PowerActionNone;
|
||
|
||
m_PnpStateCallbacks = NULL;
|
||
m_PowerStateCallbacks = NULL;
|
||
m_PowerPolicyStateCallbacks = NULL;
|
||
|
||
m_SelfManagedIoMachine = NULL;
|
||
|
||
m_EnumInfo = NULL;
|
||
|
||
m_Resources = NULL;
|
||
m_ResourcesRaw = NULL;
|
||
|
||
InitializeListHead(&m_InterruptListHead);
|
||
m_InterruptObjectCount = 0;
|
||
m_WakeInterruptCount = 0;
|
||
m_WakeInterruptPendingAckCount = 0;
|
||
m_SystemWokenByWakeInterrupt = FALSE;
|
||
m_WakeInterruptsKeepConnected = FALSE;
|
||
m_AchievedStart = FALSE;
|
||
|
||
m_SharedPower.m_WaitWakeIrp = NULL;
|
||
m_SharedPower.m_WaitWakeOwner = FALSE;
|
||
m_SharedPower.m_ExtendWatchDogTimer = FALSE;
|
||
|
||
m_DeviceRemoveProcessed = NULL;
|
||
|
||
#if (FX_CORE_MODE==FX_CORE_KERNEL_MODE)
|
||
//
|
||
// Interrupt APIs for Vista and forward
|
||
//
|
||
m_IoConnectInterruptEx = FxLibraryGlobals.IoConnectInterruptEx;
|
||
m_IoDisconnectInterruptEx = FxLibraryGlobals.IoDisconnectInterruptEx;
|
||
|
||
//
|
||
// Interrupt APIs for Windows 8 and forward
|
||
//
|
||
m_IoReportInterruptActive = FxLibraryGlobals.IoReportInterruptActive;
|
||
m_IoReportInterruptInactive = FxLibraryGlobals.IoReportInterruptInactive;
|
||
|
||
#endif
|
||
|
||
m_ReleaseHardwareAfterDescendantsOnFailure = FALSE;
|
||
|
||
MarkDisposeOverride(ObjectDoNotLock);
|
||
}
|
||
|
||
FxPkgPnp::~FxPkgPnp()
|
||
{
|
||
PSINGLE_LIST_ENTRY ple;
|
||
|
||
Mx::MxAssert(Mx::MxGetCurrentIrql() == PASSIVE_LEVEL);
|
||
|
||
//
|
||
// We should either have zero pending children or we never made it out of
|
||
// the init state during a failed WDFDEVICE create or failed EvtDriverDeviceAdd
|
||
//
|
||
Mx::MxAssert(m_PendingChildCount == 0 ||
|
||
m_Device->GetDevicePnpState() == WdfDevStatePnpInit);
|
||
|
||
|
||
ple = m_DeviceInterfaceHead.Next;
|
||
while (ple != NULL) {
|
||
FxDeviceInterface* pDI;
|
||
|
||
pDI = FxDeviceInterface::_FromEntry(ple);
|
||
|
||
//
|
||
// Advance to the next before deleting the current
|
||
//
|
||
ple = ple->Next;
|
||
|
||
//
|
||
// No longer in the list
|
||
//
|
||
pDI->m_Entry.Next = NULL;
|
||
|
||
delete pDI;
|
||
}
|
||
m_DeviceInterfaceHead.Next = NULL;
|
||
|
||
if (m_DmaEnablerList != NULL) {
|
||
delete m_DmaEnablerList;
|
||
m_DmaEnablerList = NULL;
|
||
}
|
||
|
||
if (m_RemovalDeviceList != NULL) {
|
||
delete m_RemovalDeviceList;
|
||
m_RemovalDeviceList = NULL;
|
||
}
|
||
|
||
if (m_UsageDependentDeviceList != NULL) {
|
||
delete m_UsageDependentDeviceList;
|
||
m_UsageDependentDeviceList = NULL;
|
||
}
|
||
|
||
if (m_PnpStateCallbacks != NULL) {
|
||
delete m_PnpStateCallbacks;
|
||
}
|
||
|
||
if (m_PowerStateCallbacks != NULL) {
|
||
delete m_PowerStateCallbacks;
|
||
}
|
||
|
||
if (m_PowerPolicyStateCallbacks != NULL) {
|
||
delete m_PowerPolicyStateCallbacks;
|
||
}
|
||
|
||
if (m_SelfManagedIoMachine != NULL) {
|
||
delete m_SelfManagedIoMachine;
|
||
m_SelfManagedIoMachine = NULL;
|
||
}
|
||
|
||
if (m_EnumInfo != NULL) {
|
||
delete m_EnumInfo;
|
||
m_EnumInfo = NULL;
|
||
}
|
||
|
||
if (m_Resources != NULL) {
|
||
m_Resources->RELEASE(this);
|
||
m_Resources = NULL;
|
||
}
|
||
|
||
if (m_ResourcesRaw != NULL) {
|
||
m_ResourcesRaw->RELEASE(this);
|
||
m_ResourcesRaw = NULL;
|
||
}
|
||
|
||
ASSERT(IsListEmpty(&m_InterruptListHead));
|
||
}
|
||
|
||
BOOLEAN
|
||
FxPkgPnp::Dispose(
|
||
VOID
|
||
)
|
||
{
|
||
PSINGLE_LIST_ENTRY ple;
|
||
|
||
//
|
||
// All of the interrupts were freed during this object's dispose path. This
|
||
// is because all of the interrupts were attached as children to this object.
|
||
//
|
||
// It is safe to just reinitialize the list head.
|
||
//
|
||
InitializeListHead(&m_InterruptListHead);
|
||
|
||
m_QueryInterfaceLock.AcquireLock(GetDriverGlobals());
|
||
|
||
//
|
||
// A derived class can insert an embedded FxQueryInterface into the QI list,
|
||
// so clear out the list before the destructor chain runs. (The derived
|
||
// class will be destructed first, as such, the embedded FxQueryInterface
|
||
// will also destruct first and complain it is still in a list.
|
||
//
|
||
ple = m_QueryInterfaceHead.Next;
|
||
|
||
//
|
||
// Clear out the head while holding the lock so that we synchronize against
|
||
// processing a QI while deleting the list.
|
||
//
|
||
m_QueryInterfaceHead.Next = NULL;
|
||
|
||
m_QueryInterfaceLock.ReleaseLock(GetDriverGlobals());
|
||
|
||
while (ple != NULL) {
|
||
FxQueryInterface* pQI;
|
||
|
||
pQI = FxQueryInterface::_FromEntry(ple);
|
||
|
||
//
|
||
// Advance before we potentiall free the structure
|
||
//
|
||
ple = ple->Next;
|
||
|
||
//
|
||
// FxQueryInterface's destructor requires Next be NULL
|
||
//
|
||
pQI->m_Entry.Next = NULL;
|
||
|
||
if (pQI->m_EmbeddedInterface == FALSE) {
|
||
delete pQI;
|
||
}
|
||
}
|
||
|
||
#if (FX_CORE_MODE==FX_CORE_KERNEL_MODE)
|
||
DropD3ColdInterface();
|
||
#endif
|
||
|
||
//
|
||
// Call up the hierarchy
|
||
//
|
||
return __super::Dispose();
|
||
}
|
||
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::Initialize(
|
||
__in PWDFDEVICE_INIT DeviceInit
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function initializes state associated with an instance of FxPkgPnp.
|
||
This differs from the constructor because it can do operations which
|
||
will fail, and can return failure. (Constructors can't fail, they can
|
||
only throw exceptions, which we can't deal with in this kernel mode
|
||
environment.)
|
||
|
||
Arguments:
|
||
|
||
DeviceInit - Struct that the driver initialized that contains defaults.
|
||
|
||
Returns:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
PFX_DRIVER_GLOBALS pFxDriverGlobals;
|
||
NTSTATUS status;
|
||
|
||
pFxDriverGlobals = GetDriverGlobals();
|
||
|
||
m_ReleaseHardwareAfterDescendantsOnFailure = (DeviceInit->ReleaseHardwareOrderOnFailure ==
|
||
WdfReleaseHardwareOrderOnFailureAfterDescendants);
|
||
|
||
status = m_QueryInterfaceLock.Initialize();
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR,
|
||
TRACINGPNP,
|
||
"Could not initialize QueryInterfaceLock for "
|
||
"WDFDEVICE %p, %!STATUS!",
|
||
m_Device->GetHandle(), status);
|
||
return status;
|
||
}
|
||
|
||
status = m_DeviceInterfaceLock.Initialize();
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR,
|
||
TRACINGPNP,
|
||
"Could not initialize DeviceInterfaceLock for "
|
||
"WDFDEVICE %p, %!STATUS!",
|
||
m_Device->GetHandle(), status);
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Initialize preallocated events for UM
|
||
// (For KM, events allocated on stack are used since event initialization
|
||
// doesn't fail in KM)
|
||
//
|
||
#if (FX_CORE_MODE==FX_CORE_USER_MODE)
|
||
|
||
status = m_CleanupEventUm.Initialize();
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR,
|
||
TRACINGPNP,
|
||
"Could not initialize cleanup event for "
|
||
"WDFDEVICE %p, %!STATUS!",
|
||
m_Device->GetHandle(), status);
|
||
return status;
|
||
}
|
||
|
||
status = m_RemoveEventUm.Initialize(SynchronizationEvent, FALSE);
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR,
|
||
TRACINGPNP,
|
||
"Could not initialize remove event for "
|
||
"WDFDEVICE %p, %!STATUS!",
|
||
m_Device->GetHandle(), status);
|
||
return status;
|
||
}
|
||
#endif
|
||
|
||
if (DeviceInit->IsPwrPolOwner()) {
|
||
m_PowerPolicyMachine.m_Owner = new (pFxDriverGlobals)
|
||
FxPowerPolicyOwnerSettings(this);
|
||
|
||
if (m_PowerPolicyMachine.m_Owner == NULL) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
status = m_PowerPolicyMachine.m_Owner->Init();
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
#if (FX_CORE_MODE==FX_CORE_KERNEL_MODE)
|
||
QueryForD3ColdInterface();
|
||
#endif
|
||
}
|
||
|
||
//
|
||
// we will change the access flags on the object later on when we build up
|
||
// the list from the wdm resources
|
||
//
|
||
status = FxCmResList::_CreateAndInit(&m_Resources,
|
||
pFxDriverGlobals,
|
||
m_Device,
|
||
WDF_NO_OBJECT_ATTRIBUTES,
|
||
FxResourceNoAccess);
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
status = m_Resources->Commit(WDF_NO_OBJECT_ATTRIBUTES,
|
||
WDF_NO_HANDLE,
|
||
m_Device);
|
||
|
||
//
|
||
// This should never fail
|
||
//
|
||
ASSERT(NT_SUCCESS(status));
|
||
if (!NT_SUCCESS(status)) {
|
||
m_Resources->DeleteFromFailedCreate();
|
||
m_Resources = NULL;
|
||
return status;
|
||
}
|
||
|
||
m_Resources->ADDREF(this);
|
||
|
||
//
|
||
// we will change the access flags on the object later on when we build up
|
||
// the list from the wdm resources
|
||
//
|
||
status = FxCmResList::_CreateAndInit(&m_ResourcesRaw,
|
||
pFxDriverGlobals,
|
||
m_Device,
|
||
WDF_NO_OBJECT_ATTRIBUTES,
|
||
FxResourceNoAccess);
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
status = m_ResourcesRaw->Commit(WDF_NO_OBJECT_ATTRIBUTES,
|
||
WDF_NO_HANDLE,
|
||
m_Device);
|
||
|
||
//
|
||
// This should never fail
|
||
//
|
||
ASSERT(NT_SUCCESS(status));
|
||
if (!NT_SUCCESS(status)) {
|
||
m_ResourcesRaw->DeleteFromFailedCreate();
|
||
m_ResourcesRaw = NULL;
|
||
return status;
|
||
}
|
||
|
||
m_ResourcesRaw->ADDREF(this);
|
||
|
||
status = RegisterCallbacks(&DeviceInit->PnpPower.PnpPowerEventCallbacks);
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
if (IsPowerPolicyOwner()) {
|
||
RegisterPowerPolicyCallbacks(&DeviceInit->PnpPower.PolicyEventCallbacks);
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::Dispatch(
|
||
__in MdIrp Irp
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the main dispatch handler for the pnp package. This method is
|
||
called by the framework manager when a PNP or Power IRP enters the driver.
|
||
This function will dispatch the IRP to a function designed to handle the
|
||
specific IRP.
|
||
|
||
Arguments:
|
||
|
||
Device - a pointer to the FxDevice
|
||
|
||
Irp - a pointer to the FxIrp
|
||
|
||
Returns:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
FxIrp irp(Irp);
|
||
|
||
#if (FX_CORE_MODE==FX_CORE_KERNEL_MODE)
|
||
FX_TRACK_DRIVER(GetDriverGlobals());
|
||
#endif
|
||
|
||
if (irp.GetMajorFunction() == IRP_MJ_PNP) {
|
||
|
||
switch (irp.GetMinorFunction()) {
|
||
case IRP_MN_START_DEVICE:
|
||
case IRP_MN_QUERY_REMOVE_DEVICE:
|
||
case IRP_MN_REMOVE_DEVICE:
|
||
case IRP_MN_CANCEL_REMOVE_DEVICE:
|
||
case IRP_MN_STOP_DEVICE:
|
||
case IRP_MN_QUERY_STOP_DEVICE:
|
||
case IRP_MN_CANCEL_STOP_DEVICE:
|
||
case IRP_MN_SURPRISE_REMOVAL:
|
||
case IRP_MN_EJECT:
|
||
case IRP_MN_QUERY_PNP_DEVICE_STATE:
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE 0x%p !devobj 0x%p, IRP_MJ_PNP, %!pnpmn! IRP 0x%p",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject(),
|
||
irp.GetMinorFunction(), irp.GetIrp());
|
||
break;
|
||
|
||
case IRP_MN_QUERY_DEVICE_RELATIONS:
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE 0x%p !devobj 0x%p, IRP_MJ_PNP, %!pnpmn! "
|
||
"type %!DEVICE_RELATION_TYPE! IRP 0x%p",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject(),
|
||
irp.GetMinorFunction(),
|
||
irp.GetParameterQDRType(), irp.GetIrp());
|
||
break;
|
||
|
||
default:
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"WDFDEVICE 0x%p !devobj 0x%p, IRP_MJ_PNP, %!pnpmn! IRP 0x%p",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject(),
|
||
irp.GetMinorFunction(), irp.GetIrp());
|
||
break;
|
||
}
|
||
|
||
if (irp.GetMinorFunction() <= IRP_MN_SURPRISE_REMOVAL) {
|
||
status = (*GetDispatchPnp()[irp.GetMinorFunction()])(this, &irp);
|
||
}
|
||
else {
|
||
//
|
||
// For Pnp IRPs we don't understand, just forget about them
|
||
//
|
||
status = FireAndForgetIrp(&irp);
|
||
}
|
||
}
|
||
else {
|
||
//
|
||
// If this isn't a PnP Irp, it must be a power irp.
|
||
//
|
||
switch (irp.GetMinorFunction()) {
|
||
case IRP_MN_WAIT_WAKE:
|
||
case IRP_MN_SET_POWER:
|
||
if (irp.GetParameterPowerType() == SystemPowerState) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE 0x%p !devobj 0x%p IRP_MJ_POWER, %!pwrmn! "
|
||
"IRP 0x%p for %!SYSTEM_POWER_STATE! (S%d)",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject(),
|
||
irp.GetMinorFunction(), irp.GetIrp(),
|
||
irp.GetParameterPowerStateSystemState(),
|
||
irp.GetParameterPowerStateSystemState() - 1);
|
||
}
|
||
else {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE 0x%p !devobj 0x%p IRP_MJ_POWER, %!pwrmn! "
|
||
"IRP 0x%p for %!DEVICE_POWER_STATE!",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject(),
|
||
irp.GetMinorFunction(), irp.GetIrp(),
|
||
irp.GetParameterPowerStateDeviceState());
|
||
}
|
||
break;
|
||
|
||
default:
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"WDFDEVICE 0x%p !devobj 0x%p IRP_MJ_POWER, %!pwrmn! IRP 0x%p",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject(),
|
||
irp.GetMinorFunction(), irp.GetIrp());
|
||
break;
|
||
}
|
||
|
||
Mx::MxAssert(irp.GetMajorFunction() == IRP_MJ_POWER);
|
||
|
||
if (irp.GetMinorFunction() <= IRP_MN_QUERY_POWER) {
|
||
status = (*GetDispatchPower()[irp.GetMinorFunction()])(this, &irp);
|
||
}
|
||
else {
|
||
//
|
||
// For Power IRPs we don't understand, just forget about them
|
||
//
|
||
status = FireAndForgetIrp(&irp);
|
||
}
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
PNP_DEVICE_STATE
|
||
FxPkgPnp::HandleQueryPnpDeviceState(
|
||
__in PNP_DEVICE_STATE PnpDeviceState
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function handled IRP_MN_QUERY_DEVICE_STATE. Most of the bits are
|
||
just copied from internal Framework state.
|
||
|
||
Arguments:
|
||
|
||
PnpDeviceState - Bitfield that will be returned to the sender of the IRP.
|
||
|
||
Returns:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
LONG state;
|
||
|
||
state = GetPnpStateInternal();
|
||
|
||
//
|
||
// Return device state set by driver.
|
||
//
|
||
SET_PNP_DEVICE_STATE_BIT(&PnpDeviceState,
|
||
PNP_DEVICE_DISABLED,
|
||
state,
|
||
Disabled);
|
||
SET_PNP_DEVICE_STATE_BIT(&PnpDeviceState,
|
||
PNP_DEVICE_DONT_DISPLAY_IN_UI,
|
||
state,
|
||
DontDisplayInUI);
|
||
SET_PNP_DEVICE_STATE_BIT(&PnpDeviceState,
|
||
PNP_DEVICE_FAILED,
|
||
state,
|
||
Failed);
|
||
SET_PNP_DEVICE_STATE_BIT(&PnpDeviceState,
|
||
PNP_DEVICE_NOT_DISABLEABLE,
|
||
state,
|
||
NotDisableable);
|
||
SET_PNP_DEVICE_STATE_BIT(&PnpDeviceState,
|
||
PNP_DEVICE_REMOVED,
|
||
state,
|
||
Removed);
|
||
SET_PNP_DEVICE_STATE_BIT(&PnpDeviceState,
|
||
PNP_DEVICE_RESOURCE_REQUIREMENTS_CHANGED,
|
||
state,
|
||
ResourcesChanged);
|
||
|
||
if ((state & FxPnpStateDontDisplayInUIMask) == FxPnpStateDontDisplayInUIUseDefault) {
|
||
LONG caps;
|
||
|
||
//
|
||
// Mask off all caps except for NoDispalyInUI
|
||
//
|
||
caps = GetPnpCapsInternal() & FxPnpCapNoDisplayInUIMask;
|
||
|
||
//
|
||
// If the driver didn't specify pnp state, see if they specified no
|
||
// display as a capability. For raw PDOs and just usability, it is not
|
||
// always clear to the driver writer that they must set both the pnp cap
|
||
// and the pnp state for the no display in UI property to stick after
|
||
// the device has been started.
|
||
//
|
||
if (caps == FxPnpCapNoDisplayInUITrue) {
|
||
PnpDeviceState |= PNP_DEVICE_DONT_DISPLAY_IN_UI;
|
||
}
|
||
else if (caps == FxPnpCapNoDisplayInUIFalse) {
|
||
PnpDeviceState &= ~PNP_DEVICE_DONT_DISPLAY_IN_UI;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Return device state maintained by frameworks.
|
||
//
|
||
if (IsInSpecialUse()) {
|
||
PnpDeviceState |= PNP_DEVICE_NOT_DISABLEABLE;
|
||
}
|
||
|
||
//
|
||
// If there is an internal failure, then indicate that up to pnp.
|
||
//
|
||
if (m_InternalFailure || m_Failed) {
|
||
PnpDeviceState |= PNP_DEVICE_FAILED;
|
||
}
|
||
|
||
return PnpDeviceState;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::HandleQueryBusRelations(
|
||
__inout FxIrp* Irp
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Handles a query device relations for the bus relations type (all other types
|
||
are handled by HandleQueryDeviceRelations). This function will call into
|
||
each of the device's FxChildList objects to process the request.
|
||
|
||
Arguments:
|
||
Irp - the request contain the query device relations
|
||
|
||
Return Value:
|
||
NTSTATUS
|
||
|
||
--*/
|
||
{
|
||
FxWaitLockTransactionedList* pList;
|
||
PDEVICE_RELATIONS pRelations;
|
||
FxTransactionedEntry* ple;
|
||
NTSTATUS status, listStatus;
|
||
BOOLEAN changed;
|
||
|
||
//
|
||
// Before we do anything, callback into the driver
|
||
//
|
||
m_DeviceRelationsQuery.Invoke(m_Device->GetHandle(), BusRelations);
|
||
|
||
//
|
||
// Default to success unless list processing fails
|
||
//
|
||
status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Keep track of changes made by any list object. If anything changes,
|
||
// remember it for post-processing.
|
||
//
|
||
changed = FALSE;
|
||
|
||
pRelations = (PDEVICE_RELATIONS) Irp->GetInformation();
|
||
|
||
if (m_EnumInfo != NULL) {
|
||
pList = &m_EnumInfo->m_ChildListList;
|
||
|
||
pList->LockForEnum(GetDriverGlobals());
|
||
}
|
||
else {
|
||
pList = NULL;
|
||
}
|
||
|
||
ple = NULL;
|
||
while (pList != NULL && (ple = pList->GetNextEntry(ple)) != NULL) {
|
||
FxChildList* pChildList;
|
||
|
||
pChildList = FxChildList::_FromEntry(ple);
|
||
|
||
//
|
||
// ProcessBusRelations will free and reallocate pRelations if necessary
|
||
//
|
||
listStatus = pChildList->ProcessBusRelations(&pRelations);
|
||
|
||
//
|
||
// STATUS_NOT_SUPPORTED is a special value. It indicates that the call
|
||
// to ProcessBusRelations did not modify pRelations in any way.
|
||
//
|
||
if (listStatus == STATUS_NOT_SUPPORTED) {
|
||
continue;
|
||
}
|
||
|
||
|
||
if (!NT_SUCCESS(listStatus)) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFDEVICE %p, WDFCHILDLIST %p returned %!STATUS! from "
|
||
"processing bus relations",
|
||
m_Device->GetHandle(), pChildList->GetHandle(), listStatus);
|
||
status = listStatus;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// We updated pRelations, change the status later
|
||
//
|
||
changed = TRUE;
|
||
}
|
||
|
||
//
|
||
// By checking for NT_SUCCESS(status) below we account for
|
||
// both the cases - list changed, as well as list unchanged but possibly
|
||
// children being reported missing (that doesn't involve list change).
|
||
//
|
||
if (NT_SUCCESS(status)) {
|
||
|
||
ple = NULL;
|
||
while (pList != NULL && (ple = pList->GetNextEntry(ple)) != NULL) {
|
||
FxChildList* pChildList;
|
||
|
||
pChildList = FxChildList::_FromEntry(ple);
|
||
|
||
//
|
||
// invoke the ReportedMissing callback for for children that are
|
||
// being reporte missing
|
||
//
|
||
pChildList->InvokeReportedMissingCallback();
|
||
}
|
||
}
|
||
|
||
if (pList != NULL) {
|
||
pList->UnlockFromEnum(GetDriverGlobals());
|
||
}
|
||
|
||
if (NT_SUCCESS(status) && changed == FALSE) {
|
||
//
|
||
// Went through the entire list of FxChildList objects, but no object
|
||
// modified the pRelations, so restore the caller's NTSTATUS.
|
||
//
|
||
status = Irp->GetStatus();
|
||
}
|
||
|
||
//
|
||
// Re-set the relations into the structure so that any changes that any call
|
||
// to FxChildList::ProcessBusRelations takes effect and is reported.
|
||
//
|
||
Irp->SetInformation((ULONG_PTR) pRelations);
|
||
Irp->SetStatus(status);
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"WDFDEVICE %p, returning %!STATUS! from processing bus relations",
|
||
m_Device->GetHandle(), status
|
||
);
|
||
|
||
if (NT_SUCCESS(status) && pRelations != NULL) {
|
||
ULONG i;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE %p returning %d devices in relations %p",
|
||
m_Device->GetHandle(), pRelations->Count, pRelations
|
||
);
|
||
|
||
//
|
||
// Try to not consume an IFR entry per DO reported. Instead, report up
|
||
// to 4 at a time.
|
||
//
|
||
for (i = 0; i < pRelations->Count && GetDriverGlobals()->FxVerboseOn; i += 4) {
|
||
if (i + 3 < pRelations->Count) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"PDO %p PDO %p PDO %p PDO %p",
|
||
pRelations->Objects[i],
|
||
pRelations->Objects[i+1],
|
||
pRelations->Objects[i+2],
|
||
pRelations->Objects[i+3]
|
||
);
|
||
}
|
||
else if (i + 2 < pRelations->Count) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"PDO %p PDO %p PDO %p",
|
||
pRelations->Objects[i],
|
||
pRelations->Objects[i+1],
|
||
pRelations->Objects[i+2]
|
||
);
|
||
}
|
||
else if (i + 1 < pRelations->Count) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"PDO %p PDO %p",
|
||
pRelations->Objects[i],
|
||
pRelations->Objects[i+1]
|
||
);
|
||
}
|
||
else {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"PDO %p",
|
||
pRelations->Objects[i]
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::HandleQueryBusInformation(
|
||
__inout FxIrp* Irp
|
||
)
|
||
{
|
||
NTSTATUS status;
|
||
|
||
//
|
||
// Probably is a better check then this to see if the driver set the bus
|
||
// information
|
||
//
|
||
if (m_BusInformation.BusTypeGuid.Data1 != 0x0) {
|
||
PPNP_BUS_INFORMATION pBusInformation;
|
||
PFX_DRIVER_GLOBALS pFxDriverGlobals;
|
||
|
||
pFxDriverGlobals = GetDriverGlobals();
|
||
pBusInformation = (PPNP_BUS_INFORMATION) MxMemory::MxAllocatePoolWithTag(
|
||
PagedPool, sizeof(PNP_BUS_INFORMATION), pFxDriverGlobals->Tag);
|
||
|
||
if (pBusInformation != NULL) {
|
||
//
|
||
// Initialize the PNP_BUS_INFORMATION structure with the data
|
||
// from PDO properties.
|
||
//
|
||
RtlCopyMemory(pBusInformation,
|
||
&m_BusInformation,
|
||
sizeof(PNP_BUS_INFORMATION));
|
||
|
||
Irp->SetInformation((ULONG_PTR) pBusInformation);
|
||
status = STATUS_SUCCESS;
|
||
}
|
||
else {
|
||
Irp->SetInformation(NULL);
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
||
DoTraceLevelMessage(
|
||
pFxDriverGlobals, TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFDEVICE %p could not allocate PNP_BUS_INFORMATION string, "
|
||
" %!STATUS!", m_Device->GetHandle(), status);
|
||
}
|
||
}
|
||
else {
|
||
status = Irp->GetStatus();
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::HandleQueryDeviceRelations(
|
||
__inout FxIrp* Irp,
|
||
__inout FxRelatedDeviceList* List
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Handles the query device relations request for all relation types *except*
|
||
for bus relations (HandleQueryBusRelations handles that type exclusively).
|
||
|
||
This function will allocate a PDEVICE_RELATIONS structure if the passed in
|
||
FxRelatedDeviceList contains any devices to add to the relations list.
|
||
|
||
Arguments:
|
||
Irp - the request
|
||
|
||
List - list containing devices to report in the relations
|
||
|
||
Return Value:
|
||
NTSTATUS
|
||
|
||
--*/
|
||
{
|
||
PDEVICE_RELATIONS pPriorRelations, pNewRelations;
|
||
FxRelatedDevice* entry;
|
||
DEVICE_RELATION_TYPE type;
|
||
ULONG count;
|
||
size_t size;
|
||
NTSTATUS status;
|
||
BOOLEAN retry;
|
||
PFX_DRIVER_GLOBALS pFxDriverGlobals;
|
||
|
||
if (List == NULL) {
|
||
//
|
||
// Indicate that we didn't modify the irp at all since we have no list
|
||
//
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
pFxDriverGlobals = GetDriverGlobals();
|
||
type = Irp->GetParameterQDRType();
|
||
status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Notify driver that he should re-scan for device relations.
|
||
//
|
||
m_DeviceRelationsQuery.Invoke(m_Device->GetHandle(), type);
|
||
|
||
pPriorRelations = (PDEVICE_RELATIONS) Irp->GetInformation();
|
||
retry = FALSE;
|
||
|
||
count = 0;
|
||
|
||
List->LockForEnum(pFxDriverGlobals);
|
||
|
||
//
|
||
// Count how many entries there are in the list
|
||
//
|
||
for (entry = NULL; (entry = List->GetNextEntry(entry)) != NULL; count++) {
|
||
DO_NOTHING();
|
||
}
|
||
|
||
//
|
||
// If we have
|
||
// 1) no devices in the list AND
|
||
// a) we have nothing to report OR
|
||
// b) we have something to report and there are previous relations (which
|
||
// if left unchanged will be used to report our missing devices)
|
||
//
|
||
// THEN we have nothing else to do, just return
|
||
//
|
||
if (count == 0 &&
|
||
(List->m_NeedReportMissing == 0 || pPriorRelations != NULL)) {
|
||
List->UnlockFromEnum(pFxDriverGlobals);
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
if (pPriorRelations != NULL) {
|
||
//
|
||
// Looks like another driver in the stack has already added some
|
||
// entries. Make sure we allocate space for these additional entries.
|
||
//
|
||
count += pPriorRelations->Count;
|
||
}
|
||
|
||
//
|
||
// Allocate space for the device relations structure (which includes
|
||
// space for one PDEVICE_OBJECT, and then allocate enough additional
|
||
// space for the extra PDEVICE_OBJECTS we need.
|
||
//
|
||
// (While no FxChildList objects are used in this function, this static
|
||
// function from the class computes what we need.)
|
||
//
|
||
size = FxChildList::_ComputeRelationsSize(count);
|
||
|
||
pNewRelations = (PDEVICE_RELATIONS) MxMemory::MxAllocatePoolWithTag(
|
||
PagedPool, size, pFxDriverGlobals->Tag);
|
||
|
||
if (pNewRelations == NULL) {
|
||
//
|
||
// Dereference any previously reported relations before exiting. They
|
||
// are dereferenced here because the PNP manager will see error and not
|
||
// do anything while the driver which added these objects expects the
|
||
// pnp manager to do the dereference. Since this device is changing the
|
||
// status, it must act like the pnp manager.
|
||
//
|
||
if (pPriorRelations != NULL) {
|
||
ULONG i;
|
||
|
||
for (i = 0; i < pPriorRelations->Count; i++) {
|
||
Mx::MxDereferenceObject(pPriorRelations->Objects[i]);
|
||
}
|
||
}
|
||
|
||
if (List->IncrementRetries() < 3) {
|
||
retry = TRUE;
|
||
}
|
||
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
||
DoTraceLevelMessage(
|
||
pFxDriverGlobals, TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFDEVICE %p could not allocate device relations for type %d string, "
|
||
" %!STATUS!", m_Device->GetHandle(), type, status);
|
||
|
||
goto Done;
|
||
}
|
||
|
||
RtlZeroMemory(pNewRelations, size);
|
||
|
||
//
|
||
// If there was an existing device relations structure, copy
|
||
// the entries to the new structure.
|
||
//
|
||
if (pPriorRelations != NULL && pPriorRelations->Count > 0) {
|
||
RtlCopyMemory(
|
||
pNewRelations,
|
||
pPriorRelations,
|
||
FxChildList::_ComputeRelationsSize(pPriorRelations->Count)
|
||
);
|
||
}
|
||
|
||
//
|
||
// Walk the list and return the relations here
|
||
//
|
||
for (entry = NULL;
|
||
(entry = List->GetNextEntry(entry)) != NULL;
|
||
pNewRelations->Count++) {
|
||
MdDeviceObject pdo;
|
||
|
||
pdo = entry->GetDevice();
|
||
|
||
if (entry->m_State == RelatedDeviceStateNeedsReportPresent) {
|
||
entry->m_State = RelatedDeviceStateReportedPresent;
|
||
}
|
||
|
||
//
|
||
// Add it to the DEVICE_RELATIONS structure. Pnp dictates that each
|
||
// PDO in the list be referenced.
|
||
//
|
||
pNewRelations->Objects[pNewRelations->Count] = reinterpret_cast<PDEVICE_OBJECT>(pdo);
|
||
Mx::MxReferenceObject(pdo);
|
||
}
|
||
|
||
Done:
|
||
if (NT_SUCCESS(status)) {
|
||
List->ZeroRetries();
|
||
}
|
||
|
||
List->UnlockFromEnum(GetDriverGlobals());
|
||
|
||
if (pPriorRelations != NULL) {
|
||
MxMemory::MxFreePool(pPriorRelations);
|
||
}
|
||
|
||
if (retry) {
|
||
MxDeviceObject physicalDeviceObject(
|
||
m_Device->GetPhysicalDevice()
|
||
);
|
||
|
||
physicalDeviceObject.InvalidateDeviceRelations(type);
|
||
}
|
||
|
||
Irp->SetStatus(status);
|
||
Irp->SetInformation((ULONG_PTR) pNewRelations);
|
||
|
||
return status;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::PostCreateDeviceInitialize(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function does any initialization to this object which must be done
|
||
after the underlying device object has been attached to the device stack,
|
||
i.e. you can send IRPs down this stack now.
|
||
|
||
Arguments:
|
||
|
||
none
|
||
|
||
Returns:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
|
||
status = m_PnpMachine.Init(this, &FxPkgPnp::_PnpProcessEventInner);
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"PnP State Machine init failed, %!STATUS!",
|
||
status);
|
||
return status;
|
||
}
|
||
|
||
status = m_PowerMachine.Init(this, &FxPkgPnp::_PowerProcessEventInner);
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"Power State Machine init failed, %!STATUS!",
|
||
status);
|
||
return status;
|
||
}
|
||
|
||
status = m_PowerPolicyMachine.Init(this, &FxPkgPnp::_PowerPolicyProcessEventInner);
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"Power Policy State Machine init failed, %!STATUS!",
|
||
status);
|
||
return status;
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::FinishInitialize(
|
||
__inout PWDFDEVICE_INIT DeviceInit
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Finish initializing the object. All initialization up until this point
|
||
could fail. This function cannot fail, so all it can do is assign field
|
||
values and take allocations from DeviceInit.
|
||
|
||
Arguments:
|
||
DeviceInit - device initialization structure that the driver writer has
|
||
initialized
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// Reassign the state callback arrays away from the init struct.
|
||
// Set the init field to NULL so that it does not attempt to free the array
|
||
// when it is destroyed.
|
||
//
|
||
m_PnpStateCallbacks = DeviceInit->PnpPower.PnpStateCallbacks;
|
||
DeviceInit->PnpPower.PnpStateCallbacks = NULL;
|
||
|
||
m_PowerStateCallbacks = DeviceInit->PnpPower.PowerStateCallbacks;
|
||
DeviceInit->PnpPower.PowerStateCallbacks = NULL;
|
||
|
||
m_PowerPolicyStateCallbacks = DeviceInit->PnpPower.PowerPolicyStateCallbacks;
|
||
DeviceInit->PnpPower.PowerPolicyStateCallbacks = NULL;
|
||
|
||
//
|
||
// Bias the count towards one so that we can optimize the synchronous
|
||
// cleanup case when the device is being destroyed.
|
||
//
|
||
m_PendingChildCount = 1;
|
||
|
||
//
|
||
// Now "Add" this device in the terms that the PnP state machine uses. This
|
||
// will be in the context of an actual AddDevice function for FDOs, and
|
||
// something very much like it for PDOs.
|
||
//
|
||
// Important that the posting of the event is after setting of the state
|
||
// callback arrays so that we can guarantee that any state transition
|
||
// callback will be made.
|
||
//
|
||
PnpProcessEvent(PnpEventAddDevice);
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::ProcessDelayedDeletion(
|
||
VOID
|
||
)
|
||
{
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE %p, !devobj %p processing delayed deletion from pnp state "
|
||
"machine", m_Device->GetHandle(), m_Device->GetDeviceObject());
|
||
|
||
CleanupStateMachines(FALSE);
|
||
DeleteDevice();
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::SetSpecialFileSupport(
|
||
__in WDF_SPECIAL_FILE_TYPE FileType,
|
||
__in BOOLEAN Supported
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function marks the device as capable of handling the paging path,
|
||
hibernation or crashdumps. Any device that is necessary for one of these
|
||
three things will get notification. It is then responsible for forwarding
|
||
the notification to its parent. The Framework handles that. This
|
||
function just allows a driver to tell the Framework how to respond.
|
||
|
||
|
||
Arguments:
|
||
|
||
FileType - identifies which of the special paths the device is in
|
||
Supported - Yes or No
|
||
|
||
Returns:
|
||
|
||
void
|
||
|
||
--*/
|
||
|
||
{
|
||
switch (FileType) {
|
||
case WdfSpecialFilePaging:
|
||
case WdfSpecialFileHibernation:
|
||
case WdfSpecialFileDump:
|
||
case WdfSpecialFileBoot:
|
||
SetUsageSupport(_SpecialTypeToUsage(FileType), Supported);
|
||
break;
|
||
|
||
default:
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"Invalid special file type %x", FileType);
|
||
}
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
PnpPassThroughQI(
|
||
__in CfxDevice* Device,
|
||
__inout FxIrp* Irp
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This driver may be sent IRP_MN_QUERY_INTERFACE, which is a way of getting
|
||
a direct-call table from some driver in a device stack. In some cases, the
|
||
right response is to turn around and send a similar query to the device's
|
||
parent. This function does that.
|
||
|
||
|
||
Arguments:
|
||
|
||
Device - This WDFDEVICE.
|
||
Irp - The IRP that was sent to us.
|
||
|
||
Returns:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
MdIrp pFwdIrp;
|
||
NTSTATUS status;
|
||
NTSTATUS prevStatus;
|
||
MxDeviceObject pTopOfStack;
|
||
|
||
prevStatus = Irp->GetStatus();
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
pTopOfStack.SetObject(Device->m_ParentDevice->GetAttachedDeviceReference());
|
||
|
||
pFwdIrp = FxIrp::AllocateIrp(pTopOfStack.GetStackSize());
|
||
|
||
if (pFwdIrp != NULL) {
|
||
FxAutoIrp fxFwdIrp(pFwdIrp);
|
||
|
||
//
|
||
// The worker routine copies stack parameters to forward-Irp, sends it
|
||
// down the stack synchronously, then copies back the stack parameters
|
||
// from forward-irp to original-irp
|
||
//
|
||
PnpPassThroughQIWorker(&pTopOfStack, Irp, &fxFwdIrp);
|
||
|
||
if (fxFwdIrp.GetStatus() != STATUS_NOT_SUPPORTED) {
|
||
status = fxFwdIrp.GetStatus();
|
||
}
|
||
else {
|
||
status = prevStatus;
|
||
}
|
||
|
||
Irp->SetStatus(status);
|
||
Irp->SetInformation(fxFwdIrp.GetInformation());
|
||
}
|
||
else {
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
||
DoTraceLevelMessage(
|
||
Device->GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFDEVICE %p could not allocate IRP to send QI to parent !devobj "
|
||
"%p, %!STATUS!", Device->GetHandle(), pTopOfStack.GetObject(),
|
||
status);
|
||
}
|
||
|
||
pTopOfStack.DereferenceObject();
|
||
|
||
return status;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::HandleQueryInterfaceForPowerThread(
|
||
__inout FxIrp* Irp,
|
||
__out PBOOLEAN CompleteRequest
|
||
)
|
||
{
|
||
NTSTATUS status;
|
||
|
||
*CompleteRequest = TRUE;
|
||
|
||
//
|
||
// Send the request down the stack first. If someone lower handles it
|
||
// or failed trying to, just return their status
|
||
//
|
||
status = SendIrpSynchronously(Irp);
|
||
|
||
if (NT_SUCCESS(status) || status != STATUS_NOT_SUPPORTED) {
|
||
//
|
||
// Success or failure trying to handle it
|
||
//
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// The semantic of this QI is that it sent down while processing start or
|
||
// a device usage notification on the way *up* the stack. That means that
|
||
// by the time the QI gets to the lower part of the stack, the power thread
|
||
// will have already been allocated and exported.
|
||
//
|
||
ASSERT(HasPowerThread());
|
||
|
||
if (Irp->GetParameterQueryInterfaceVersion() == 1 &&
|
||
Irp->GetParameterQueryInterfaceSize() >=
|
||
m_PowerThreadInterface.Interface.Size) {
|
||
//
|
||
// Expose the interface to the requesting driver.
|
||
//
|
||
CopyQueryInterfaceToIrpStack(&m_PowerThreadInterface, Irp);
|
||
|
||
status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Caller assumes a reference has been taken.
|
||
//
|
||
m_PowerThreadInterface.Interface.InterfaceReference(
|
||
m_PowerThreadInterface.Interface.Context
|
||
);
|
||
}
|
||
else {
|
||
status = STATUS_INVALID_BUFFER_SIZE;
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::HandleQueryInterface(
|
||
__inout FxIrp* Irp,
|
||
__out PBOOLEAN CompleteRequest
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This driver may be sent IRP_MN_QUERY_INTERFACE, which is a way of getting
|
||
a direct-call table from some driver in a device stack. This function
|
||
looks into a list of interfaces that the driver has registered and, if
|
||
the interface that is being sought is present, it answers the IRP.
|
||
|
||
Arguments:
|
||
|
||
Irp - The IRP that was sent to us.
|
||
CompleteRequest - tells the caller whether the IRP should be completed
|
||
|
||
Returns:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
|
||
{
|
||
FxDeviceProcessQueryInterfaceRequest callback;
|
||
const GUID* pInterfaceType;
|
||
PSINGLE_LIST_ENTRY ple;
|
||
FxQueryInterface *pQI;
|
||
PVOID pFound;
|
||
PINTERFACE pExposedInterface;
|
||
PVOID pExposedInterfaceSpecificData;
|
||
BOOLEAN sendToParent;
|
||
|
||
NTSTATUS status;
|
||
|
||
*CompleteRequest = FALSE;
|
||
|
||
pFound = NULL;
|
||
pQI = NULL;
|
||
pExposedInterface = NULL;
|
||
pExposedInterfaceSpecificData = NULL;
|
||
sendToParent = FALSE;
|
||
|
||
pInterfaceType = Irp->GetParameterQueryInterfaceType();
|
||
//
|
||
// The power thread is special cased because it has a different semantic
|
||
// then the usual QI irp that we expose to the driver writer. In this case,
|
||
// we want to fill in the interface if the lower stack does not support it.
|
||
//
|
||
if (FxIsEqualGuid(pInterfaceType, &GUID_POWER_THREAD_INTERFACE)) {
|
||
return HandleQueryInterfaceForPowerThread(Irp, CompleteRequest);
|
||
}
|
||
else if (FxIsEqualGuid(pInterfaceType, &GUID_REENUMERATE_SELF_INTERFACE_STANDARD)) {
|
||
if (m_Device->IsPdo()) {
|
||
return ((FxPkgPdo*) this)->HandleQueryInterfaceForReenumerate(
|
||
Irp, CompleteRequest);
|
||
}
|
||
}
|
||
|
||
status = Irp->GetStatus();
|
||
|
||
//
|
||
// Walk the interface collection and return the appropriate interface.
|
||
//
|
||
m_QueryInterfaceLock.AcquireLock(GetDriverGlobals());
|
||
|
||
for (ple = m_QueryInterfaceHead.Next; ple != NULL; ple = ple->Next) {
|
||
pQI = FxQueryInterface::_FromEntry(ple);
|
||
|
||
if (FxIsEqualGuid(Irp->GetParameterQueryInterfaceType(),
|
||
&pQI->m_InterfaceType)) {
|
||
|
||
pExposedInterface = Irp->GetParameterQueryInterfaceInterface();
|
||
pExposedInterfaceSpecificData =
|
||
Irp->GetParameterQueryInterfaceInterfaceSpecificData();
|
||
|
||
if (pQI->m_Interface != NULL) {
|
||
//
|
||
// NOTE: If a driver has exposed the same interface GUID with
|
||
// different sizes as a ways of versioning, then the driver
|
||
// writer can specify the minimum size and version number
|
||
// and then fill in the remaining fields in the callback
|
||
// below.
|
||
//
|
||
if (pQI->m_Interface->Size <= Irp->GetParameterQueryInterfaceSize() &&
|
||
pQI->m_Interface->Version <= Irp->GetParameterQueryInterfaceVersion()) {
|
||
|
||
if (pQI->m_ImportInterface == FALSE) {
|
||
//
|
||
// Expose the interface to the requesting driver.
|
||
//
|
||
RtlCopyMemory(pExposedInterface,
|
||
pQI->m_Interface,
|
||
pQI->m_Interface->Size);
|
||
}
|
||
else {
|
||
//
|
||
// The interface contains data which the driver wants
|
||
// before copying over its information, so don't do a
|
||
// copy and let the event callback do the copy
|
||
//
|
||
DO_NOTHING();
|
||
}
|
||
}
|
||
else {
|
||
status = STATUS_INVALID_BUFFER_SIZE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
callback.m_Method = pQI->m_ProcessRequest.m_Method;
|
||
sendToParent = pQI->m_SendQueryToParentStack;
|
||
pFound = pQI;
|
||
|
||
status = STATUS_SUCCESS;
|
||
break;
|
||
}
|
||
}
|
||
|
||
m_QueryInterfaceLock.ReleaseLock(GetDriverGlobals());
|
||
|
||
if (!NT_SUCCESS(status) || pFound == NULL) {
|
||
goto Done;
|
||
}
|
||
|
||
//
|
||
// Let the driver see the interface before it is handed out.
|
||
//
|
||
status = callback.Invoke(m_Device->GetHandle(),
|
||
(LPGUID) Irp->GetParameterQueryInterfaceType(),
|
||
pExposedInterface,
|
||
pExposedInterfaceSpecificData);
|
||
|
||
//
|
||
// STATUS_NOT_SUPPORTED is a special cased error code which indicates that
|
||
// the QI should travel down the rest of the stack.
|
||
//
|
||
if (!NT_SUCCESS(status) && status != STATUS_NOT_SUPPORTED) {
|
||
goto Done;
|
||
}
|
||
|
||
//
|
||
// If it is meant for the parent, send it down the parent stack
|
||
//
|
||
if (sendToParent) {
|
||
status = PnpPassThroughQI(m_Device, Irp);
|
||
goto Done;
|
||
}
|
||
|
||
//
|
||
// Reference the interface before returning it to the requesting driver.
|
||
// If this is an import interface, the event callback is free to not fill
|
||
// in the InterfaceReference function pointer.
|
||
//
|
||
if (pExposedInterface->InterfaceReference != NULL) {
|
||
pExposedInterface->InterfaceReference(pExposedInterface->Context);
|
||
}
|
||
|
||
//
|
||
// If we are not a PDO in the stack, then send the fully formatted QI request
|
||
// down the stack to allow others to filter the interface.
|
||
//
|
||
if (m_Device->IsPdo() == FALSE) {
|
||
ASSERT(NT_SUCCESS(status) || status == STATUS_NOT_SUPPORTED);
|
||
|
||
Irp->SetStatus(status);
|
||
Irp->CopyCurrentIrpStackLocationToNext();
|
||
status = Irp->SendIrpSynchronously(m_Device->GetAttachedDevice());
|
||
}
|
||
|
||
Done:
|
||
if (pFound != NULL) {
|
||
*CompleteRequest = TRUE;
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::QueryForCapabilities(
|
||
VOID
|
||
)
|
||
{
|
||
STACK_DEVICE_CAPABILITIES caps;
|
||
NTSTATUS status;
|
||
|
||
MxDeviceObject deviceObject;
|
||
|
||
deviceObject.SetObject(m_Device->GetDeviceObject());
|
||
|
||
status = GetStackCapabilities(GetDriverGlobals(),
|
||
&deviceObject,
|
||
&m_D3ColdInterface,
|
||
&caps);
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
ULONG states, i;
|
||
|
||
ASSERT(caps.DeviceCaps.DeviceWake <= 0xFF && caps.DeviceCaps.SystemWake <= 0xFF);
|
||
|
||
m_SystemWake = (BYTE) caps.DeviceCaps.SystemWake;
|
||
|
||
//
|
||
// Initialize the array of wakeable D-states to say that all system
|
||
// states down to the one identified in the caps can generate wake.
|
||
// This will be overridden below if the BIOS supplied more information.
|
||
//
|
||
// Compatibility Note: Non-ACPI bus drivers (root-enumerated drivers)
|
||
// or other bus drivers that haven't set the power settings correctly
|
||
// for their PDO may end up with a valid value for DeviceWake in the
|
||
// device capabilities but a value of PowerSystemUnspecified for
|
||
// SystemWake, in which case a call to WdfDeviceAssignS0IdleSettings or
|
||
// WdfDeviceAssignSxWakeSettings DDIs will fail on 1.11+ resulting in
|
||
// device compat issues. The failure is expected and right thing to do
|
||
// but has compatibility implications - drivers that worked earlier now
|
||
// fail on 1.11. Note that earlier versions of WDF did not have
|
||
// m_DeviceWake as an array and stored just the capabilities->DeviceWake
|
||
// value without regard to the SystemWake but the current implementation
|
||
// introduces dependency on systemWake value). So for compat reasons,
|
||
// for pre-1.11 compiled drivers we initilaize the array with DeviceWake
|
||
// value ignoring SystemWake, removing any dependency of DeviceWake
|
||
// on SystemWake value and thus preserving previous behavior for
|
||
// pre-1.11 compiled drivers.
|
||
//
|
||
if (GetDriverGlobals()->IsVersionGreaterThanOrEqualTo(1,11)) {
|
||
|
||
RtlFillMemory(m_DeviceWake, DeviceWakeStates, DeviceWakeDepthNotWakeable);
|
||
|
||
for (i = PowerSystemWorking; i <= m_SystemWake; i++) {
|
||
|
||
//
|
||
// Note that this cast is hiding a conversion between two slightly
|
||
// incompatible types. DeviceWake is in terms of DEVICE_POWER_STATE
|
||
// which is defined this way:
|
||
//
|
||
// typedef enum _DEVICE_POWER_STATE {
|
||
// PowerDeviceUnspecified = 0,
|
||
// PowerDeviceD0,
|
||
// PowerDeviceD1,
|
||
// PowerDeviceD2,
|
||
// PowerDeviceD3,
|
||
// PowerDeviceMaximum
|
||
// } DEVICE_POWER_STATE, *PDEVICE_POWER_STATE;
|
||
//
|
||
// m_DeviceWake is defined in terms of DEVICE_WAKE_DEPTH which is
|
||
// defined this way:
|
||
//
|
||
// typedef enum _DEVICE_WAKE_DEPTH {
|
||
// DeviceWakeDepthNotWakeable = 0,
|
||
// DeviceWakeDepthD0,
|
||
// DeviceWakeDepthD1,
|
||
// DeviceWakeDepthD2,
|
||
// DeviceWakeDepthD3hot,
|
||
// DeviceWakeDepthD3cold,
|
||
// DeviceWakeDepthMaximum
|
||
// } DEVICE_WAKE_DEPTH, *PDEVICE_WAKE_DEPTH;
|
||
//
|
||
// The result is that the conversion below will map D3 onto D3hot,
|
||
// which is a safe assumption to start with, one which may be
|
||
// overridden later.
|
||
//
|
||
C_ASSERT(PowerDeviceD0 == DeviceWakeDepthD0);
|
||
m_DeviceWake[i - PowerSystemWorking] = (BYTE) caps.DeviceCaps.DeviceWake;
|
||
}
|
||
}
|
||
else {
|
||
//
|
||
// See comments above for information on mapping of device power
|
||
// state to device wake depth.
|
||
//
|
||
RtlFillMemory(m_DeviceWake,
|
||
DeviceWakeStates,
|
||
(BYTE) caps.DeviceCaps.DeviceWake);
|
||
}
|
||
|
||
//
|
||
// Capture the S -> D state mapping table as a ULONG for use in the
|
||
// power policy owner state machine when the machine moves into Sx and
|
||
// the device is not armed for wake and has set an IdealDxStateForSx
|
||
// value
|
||
//
|
||
states = 0x0;
|
||
|
||
for (i = 0; i < ARRAY_SIZE(caps.DeviceCaps.DeviceState); i++) {
|
||
_SetPowerCapState(i, caps.DeviceCaps.DeviceState[i], &states);
|
||
}
|
||
|
||
m_PowerPolicyMachine.m_Owner->m_SystemToDeviceStateMap = states;
|
||
|
||
//
|
||
// Query for the D3cold support interface. If present, it will tell
|
||
// us specifically which D-states will work for generating wake signals
|
||
// from specific S-states.
|
||
//
|
||
// Earlier versions of WDF didn't make this query, so for compatibility,
|
||
// we only make it now if the driver was built against WDF 1.11 or
|
||
// later. In truth, this just shifts the failure from initialization
|
||
// time to run time, because the information that we're presumably
|
||
// getting from the BIOS with this interrogation is saying that the
|
||
// code in earlier verisions of WDF would have blindly enabled a device
|
||
// for wake which simply wasn't capable of generating its wake signal
|
||
// from the chosen D-state. Thus the device would have been put into
|
||
// a low power state and then failed to resume in response to its wake
|
||
// signal.
|
||
//
|
||
|
||
if (GetDriverGlobals()->IsVersionGreaterThanOrEqualTo(1,11)) {
|
||
|
||
//
|
||
// Cycle through all the system states that this device can wake
|
||
// from. There's no need to look at deeper sleep states than
|
||
// m_SystemWake because the driver will not arm for wake in
|
||
// those states.
|
||
//
|
||
for (i = PowerSystemWorking; i <= m_SystemWake; i++) {
|
||
if (caps.DeepestWakeableDstate[i] != DeviceWakeDepthMaximum) {
|
||
m_DeviceWake[i - PowerSystemWorking] = (BYTE)caps.DeepestWakeableDstate[i];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::_PnpStartDevice(
|
||
__inout FxPkgPnp* This,
|
||
__inout FxIrp *Irp
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
This method is called in response to a PnP StartDevice IRP coming down the
|
||
stack.
|
||
|
||
Arguments:
|
||
This - device instance
|
||
Irp - a pointer to the FxIrp
|
||
|
||
Returns:
|
||
STATUS_PENDING
|
||
|
||
--*/
|
||
{
|
||
This->SetPendingPnpIrp(Irp);
|
||
This->PnpProcessEvent(PnpEventStartDevice);
|
||
|
||
return STATUS_PENDING;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::_PnpQueryStopDevice(
|
||
__inout FxPkgPnp* This,
|
||
__inout FxIrp *Irp
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Pnp callback querying to see if the device can be stopped.
|
||
|
||
The Framework philosophy surrounding Query Stop (and Query Remove) is that
|
||
it's impossible to really know if you can stop unless you've tried to stop.
|
||
This may not always be true, but it's hard to find a general strategy that
|
||
works that is less conservative. Furthermore, I couldn't find good examples
|
||
of drivers that would really benefit from continuing to handle requests
|
||
until the actual Stop IRP arrived, particularly when you consider that
|
||
most QueryStops are followed immediately by Stops.
|
||
|
||
So this function sends an event to the PnP State machine that begins the
|
||
stopping process. If it is successful, then ultimately the QueryStop IRP
|
||
will be successfully completed.
|
||
|
||
Arguments:
|
||
|
||
This - a pointer to the PnP package
|
||
|
||
Irp - a pointer to the FxIrp
|
||
|
||
Return Value:
|
||
|
||
STATUS_PENDING
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// Keep this IRP around, since we're going to deal with it later.
|
||
//
|
||
This->SetPendingPnpIrp(Irp);
|
||
|
||
//
|
||
// Now run the state machine on this thread.
|
||
//
|
||
This->PnpProcessEvent(PnpEventQueryStop);
|
||
|
||
return STATUS_PENDING;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::_PnpCancelStopDevice(
|
||
__inout FxPkgPnp* This,
|
||
__inout FxIrp *Irp
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is invoked in response to a query stop failing, somewhere in
|
||
the stack. Note that we can receive a cancel stop without being in the
|
||
query stop state if a driver above us in the stack failed the query stop.
|
||
|
||
Again, this function just exists to bridge the gap between the WDM IRPs
|
||
and the PnP state machine. This function does little more than send an
|
||
event to the machine.
|
||
|
||
Arguments:
|
||
|
||
This - the package
|
||
|
||
Irp - a pointer to the FxIrp
|
||
|
||
Returns:
|
||
|
||
STATUS_PENDING
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// Seed the irp with success
|
||
//
|
||
Irp->SetStatus(STATUS_SUCCESS);
|
||
|
||
//
|
||
// Pend it and transition the state machine
|
||
//
|
||
This->SetPendingPnpIrp(Irp);
|
||
This->PnpProcessEvent(PnpEventCancelStop);
|
||
|
||
return STATUS_PENDING;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::_PnpStopDevice(
|
||
__inout FxPkgPnp* This,
|
||
__inout FxIrp *Irp
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This method is invoked in response to a Pnp StopDevice IRP.
|
||
|
||
Arguments:
|
||
|
||
Irp - a pointer to the FxIrp
|
||
|
||
Returns:
|
||
|
||
STATUS_PENDING
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// Seed the irp with success
|
||
//
|
||
Irp->SetStatus(STATUS_SUCCESS);
|
||
|
||
//
|
||
// Pend and transition the state machine
|
||
//
|
||
This->SetPendingPnpIrp(Irp);
|
||
This->PnpProcessEvent(PnpEventStop);
|
||
|
||
return STATUS_PENDING;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::_PnpQueryRemoveDevice(
|
||
__inout FxPkgPnp* This,
|
||
__inout FxIrp *Irp
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Again, the Framework handles QueryRemove by stopping everything going on
|
||
related to the device and then asking the driver whether it can be
|
||
removed. This function just kicks the state machine. Final completion
|
||
of the IRP will come (much) later.
|
||
|
||
Arguments:
|
||
|
||
This - the package
|
||
|
||
Irp - a pointer to the FxIrp
|
||
|
||
Returns:
|
||
|
||
STATUS_PENDING
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// By default we handle this state.
|
||
//
|
||
Irp->SetStatus(STATUS_SUCCESS);
|
||
|
||
//
|
||
// Keep this IRP around, since we're going to deal with it later.
|
||
//
|
||
This->SetPendingPnpIrp(Irp);
|
||
|
||
//
|
||
// Now run the state machine on this thread.
|
||
//
|
||
This->PnpProcessEvent(PnpEventQueryRemove);
|
||
|
||
return STATUS_PENDING;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::_PnpCancelRemoveDevice(
|
||
__inout FxPkgPnp* This,
|
||
__inout FxIrp *Irp
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Notification of a previous remove being canceled. Kick the state machine.
|
||
|
||
Arguments:
|
||
|
||
This - the package
|
||
|
||
Irp - FxIrp representing the notification
|
||
|
||
Return Value:
|
||
|
||
STATUS_PENDING
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// Seed the irp with success
|
||
//
|
||
Irp->SetStatus(STATUS_SUCCESS);
|
||
|
||
//
|
||
// Pend it and transition the state machine
|
||
//
|
||
|
||
This->SetPendingPnpIrp(Irp);
|
||
This->PnpProcessEvent(PnpEventCancelRemove);
|
||
|
||
return STATUS_PENDING;
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::CleanupStateMachines(
|
||
__in BOOLEAN CleanupPnp
|
||
)
|
||
{
|
||
#if (FX_CORE_MODE==FX_CORE_USER_MODE)
|
||
FxCREvent * event = m_CleanupEventUm.GetSelfPointer();
|
||
#else
|
||
FxCREvent eventOnStack;
|
||
eventOnStack.Initialize();
|
||
FxCREvent * event = eventOnStack.GetSelfPointer();
|
||
#endif
|
||
|
||
//
|
||
// Order of shutdown is important here.
|
||
// o Pnp initiates events to power policy.
|
||
// o Power policy initiates events to power and device-power-requirement
|
||
// o Power does not initiate any events
|
||
// o Device-power-requirement does not initiate any events
|
||
//
|
||
// By shutting them down in the order in which they send events, we can
|
||
// guarantee that no new events will be posted into the subsidiary state
|
||
// machines.
|
||
//
|
||
|
||
//
|
||
// This will shut off the pnp state machine and synchronize any outstanding
|
||
// threads of execution.
|
||
//
|
||
if (CleanupPnp && m_PnpMachine.SetFinished(
|
||
event
|
||
) == FALSE) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE %p, !devobj %p waiting for pnp state machine to finish",
|
||
m_Device->GetHandle(), m_Device->GetDeviceObject());
|
||
|
||
//
|
||
// Process the event *before* completing the irp so that this event is in
|
||
// the queue before the device remove event which will be processed
|
||
// right after the start irp has been completed.
|
||
//
|
||
event->EnterCRAndWaitAndLeave();
|
||
}
|
||
|
||
//
|
||
// Even though event is a SynchronizationEvent, so we need to reset it for
|
||
// the next wait because SetFinished will set it if even if the transition
|
||
// to the finished state is immediate
|
||
//
|
||
event->Clear();
|
||
|
||
if (m_PowerPolicyMachine.SetFinished(event) == FALSE) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE %p, !devobj %p waiting for pwr pol state machine to finish",
|
||
m_Device->GetHandle(), m_Device->GetDeviceObject());
|
||
|
||
event->EnterCRAndWaitAndLeave();
|
||
}
|
||
|
||
//
|
||
// See previous comment about why we Clear()
|
||
//
|
||
event->Clear();
|
||
|
||
if (m_PowerMachine.SetFinished(event) == FALSE) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE %p, !devobj %p waiting for pwr state machine to finish",
|
||
m_Device->GetHandle(), m_Device->GetDeviceObject());
|
||
|
||
event->EnterCRAndWaitAndLeave();
|
||
}
|
||
|
||
if (IsPowerPolicyOwner()) {
|
||
//
|
||
// See previous comment about why we Clear()
|
||
//
|
||
event->Clear();
|
||
|
||
if (NULL != m_PowerPolicyMachine.m_Owner->m_PoxInterface.
|
||
m_DevicePowerRequirementMachine) {
|
||
|
||
if (FALSE == m_PowerPolicyMachine.m_Owner->m_PoxInterface.
|
||
m_DevicePowerRequirementMachine->SetFinished(event)) {
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE %p, !devobj %p waiting for device power "
|
||
"requirement state machine to finish",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject());
|
||
|
||
event->EnterCRAndWaitAndLeave();
|
||
}
|
||
}
|
||
|
||
m_PowerPolicyMachine.m_Owner->CleanupPowerCallback();
|
||
}
|
||
|
||
//
|
||
// Release the power thread if we have one either through creation or query.
|
||
// Since the power policy state machine is off, we should no longer need
|
||
// a dedicated thread.
|
||
//
|
||
// *** NOTE ***
|
||
// The power thread must be released *BEFORE* sending the irp down the stack
|
||
// because this can happen
|
||
// 1) this driver is not the power thread owner, but the last client
|
||
// 2) we send the pnp irp first
|
||
// 3) the power thread owner waits on this thread for all the clients to go
|
||
// away, but this device still has a reference on it
|
||
// 4) this device will not release the reference b/c the owner is waiting
|
||
// in the same thread.
|
||
//
|
||
ReleasePowerThread();
|
||
|
||
//
|
||
// Deref the reenumeration interface
|
||
//
|
||
ReleaseReenumerationInterface();
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::CleanupDeviceFromFailedCreate(
|
||
__in MxEvent * WaitEvent
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
The device failed creation in some stage. It is assumed that the device has
|
||
enough state that it can survive a transition through the pnp state machine
|
||
(which means that pointers like m_PkgIo are valid and != NULL). When this
|
||
function returns, it will have deleted the owning FxDevice.
|
||
|
||
Arguments:
|
||
WaitEvent - Event on which RemoveProcessed wait will be performed
|
||
|
||
We can't initialize this event on stack as the initialization
|
||
can fail in user-mode. We can't have Initialize method
|
||
preinitailize this event either as this function may get called
|
||
before initialize (or in case of initialization failure).
|
||
|
||
Hence the caller preallocates the event and passes to this
|
||
function.
|
||
|
||
Caller must initialize this event as SynchronizationEvent
|
||
and it must be unsignalled.
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
Mx::MxAssert(Mx::MxGetCurrentIrql() == PASSIVE_LEVEL);
|
||
|
||
//
|
||
// Caller must initialize the event as Synchronization event and it should
|
||
// be passed as non-signalled. But we Clear it just to be sure.
|
||
//
|
||
WaitEvent->Clear();
|
||
|
||
ADDREF(WaitEvent);
|
||
|
||
ASSERT(m_DeviceRemoveProcessed == NULL);
|
||
m_DeviceRemoveProcessed = WaitEvent;
|
||
|
||
//
|
||
// Simulate a remove event coming to the device. After this call returns
|
||
// m_Device is still valid and must be deleted.
|
||
//
|
||
PnpProcessEvent(PnpEventRemove);
|
||
|
||
//
|
||
// No need to wait in a critical region because we are in the context of a
|
||
// pnp request which is in the system context.
|
||
//
|
||
WaitEvent->WaitFor(Executive, KernelMode, FALSE, NULL);
|
||
m_DeviceRemoveProcessed = NULL;
|
||
|
||
RELEASE(WaitEvent);
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::DeleteDevice(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
This routine will detach and delete the device object and free the memory
|
||
for the device if there are no other references to it. Before calling this
|
||
routine, the state machines should have been cleaned up and the power thread
|
||
released.
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// This will detach and delete the device object
|
||
//
|
||
m_Device->Destroy();
|
||
|
||
//
|
||
// If this is the last reference, this will free the memory for the device
|
||
//
|
||
m_Device->DeleteObject();
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::_PnpRemoveDevice(
|
||
__inout FxPkgPnp* This,
|
||
__inout FxIrp *Irp
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Notification of a remove. Kick the state machine.
|
||
|
||
Arguments:
|
||
|
||
This - the package
|
||
|
||
Irp - FxIrp representing the notification
|
||
|
||
Return Value:
|
||
|
||
status
|
||
|
||
--*/
|
||
|
||
{
|
||
#if (FX_CORE_MODE==FX_CORE_USER_MODE)
|
||
MxEvent * event = This->m_RemoveEventUm.GetSelfPointer();
|
||
#else
|
||
MxEvent eventOnStack;
|
||
eventOnStack.Initialize(SynchronizationEvent, FALSE);
|
||
MxEvent * event = eventOnStack.GetSelfPointer();
|
||
#endif
|
||
|
||
NTSTATUS status;
|
||
|
||
status = Mx::MxAcquireRemoveLock(
|
||
This->m_Device->GetRemoveLock(),
|
||
Irp->GetIrp());
|
||
|
||
#if DBG
|
||
ASSERT(NT_SUCCESS(status));
|
||
#else
|
||
UNREFERENCED_PARAMETER(status);
|
||
#endif
|
||
|
||
//
|
||
// Keep this object around after m_Device has RELEASE'ed its reference to
|
||
// this package.
|
||
//
|
||
This->ADDREF(Irp);
|
||
|
||
//
|
||
// Removes are always success
|
||
//
|
||
Irp->SetStatus(STATUS_SUCCESS);
|
||
|
||
ASSERT(This->m_DeviceRemoveProcessed == NULL);
|
||
This->m_DeviceRemoveProcessed = event;
|
||
|
||
//
|
||
// Post the event and wait for the FxDevice to destroy itself or determine
|
||
// it has not been reported missing yet (for PDOs and bus filters).
|
||
//
|
||
This->PnpProcessEvent(PnpEventRemove);
|
||
|
||
DoTraceLevelMessage(
|
||
This->GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE %p, !devobj %p waiting for remove event to finish processing",
|
||
This->m_Device->GetHandle(), This->m_Device->GetDeviceObject());
|
||
|
||
//
|
||
// No need to wait in a critical region because we are in the context of a
|
||
// pnp request which is in the system context.
|
||
//
|
||
event->WaitFor(Executive, KernelMode, FALSE, NULL);
|
||
|
||
This->m_DeviceRemoveProcessed = NULL;
|
||
|
||
status = This->ProcessRemoveDeviceOverload(Irp);
|
||
|
||
//
|
||
// Release the reference added at the top. This is most likely going to be
|
||
// the last reference on the package for KMDF. For UMDF, host manages the
|
||
// lifetime of FxDevice so this may not be the last release for UMDF.
|
||
//
|
||
This->RELEASE(Irp);
|
||
|
||
return status;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::PnpSurpriseRemoval(
|
||
__inout FxIrp* Irp
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Notification that the device has been surprise removed. Kick the state
|
||
machine.
|
||
|
||
Arguments:
|
||
|
||
Irp - pointer to FxIrp representing this notification
|
||
|
||
Return Value:
|
||
|
||
STATUS_PENDING
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// Package specific handling
|
||
//
|
||
Irp->SetStatus(STATUS_SUCCESS);
|
||
SetPendingPnpIrp(Irp);
|
||
PnpProcessEvent(PnpEventSurpriseRemove);
|
||
|
||
return STATUS_PENDING;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::_DispatchWaitWake(
|
||
__inout FxPkgPnp* This,
|
||
__inout FxIrp *Irp
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This the first-level dispatch routine for IRP_MN_WAIT_WAKE. What one
|
||
does with a WaitWake IRP depends very much on whether one is an FDO, a PDO
|
||
or a filter. So dispatch immediately to a subclassable function.
|
||
|
||
Arguments:
|
||
|
||
This - the package
|
||
|
||
Irp - pointer to FxIrp representing this notification
|
||
|
||
Return Value:
|
||
|
||
status
|
||
|
||
--*/
|
||
|
||
{
|
||
return This->DispatchWaitWake(Irp);
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::DispatchWaitWake(
|
||
__inout FxIrp *Irp
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Handles wait wake requests in a generic fashion
|
||
|
||
Arguments:
|
||
|
||
|
||
Return Value:
|
||
|
||
--*/
|
||
{
|
||
PFX_DRIVER_GLOBALS FxDriverGlobals = GetDriverGlobals();
|
||
NTSTATUS status;
|
||
PIRP oldIrp;
|
||
KIRQL irql;
|
||
|
||
if (IsPowerPolicyOwner()) {
|
||
if (m_PowerPolicyMachine.m_Owner->m_RequestedWaitWakeIrp == FALSE) {
|
||
//
|
||
// A power irp arrived, but we did not request it. log and bugcheck
|
||
//
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"Received wait wake power irp %p on device %p, but the irp was "
|
||
"not requested by the device (the power policy owner)",
|
||
Irp->GetIrp(), m_Device->GetDeviceObject());
|
||
|
||
FxVerifierBugCheck(GetDriverGlobals(), // globals
|
||
WDF_POWER_MULTIPLE_PPO, // specific type
|
||
(ULONG_PTR)m_Device->GetDeviceObject(), //parm 2
|
||
(ULONG_PTR)Irp->GetIrp()); // parm 3
|
||
|
||
/* NOTREACHED */
|
||
}
|
||
|
||
//
|
||
// We are no longer requesting a power irp because we received the one
|
||
// we requested.
|
||
//
|
||
m_PowerPolicyMachine.m_Owner->m_RequestedWaitWakeIrp = FALSE;
|
||
}
|
||
|
||
//
|
||
// The Framework has the concept of a "Wait/Wake Owner." This is the layer
|
||
// in the stack that is enabling and disabling wake at the bus level. This
|
||
// is probably the PDO or a bus filter like ACPI.sys. This is distinct
|
||
// from being the "Power Policy Owner," which would mean that this driver
|
||
// is deciding what D-state is appropriate for the device, and also
|
||
// sending Wait/Wake IRPs.
|
||
//
|
||
|
||
if (m_SharedPower.m_WaitWakeOwner) {
|
||
m_PowerMachine.m_WaitWakeLock.Acquire(&irql);
|
||
|
||
if (m_SharedPower.m_WaitWakeIrp != NULL) {
|
||
//
|
||
// We only allow one pended wait wake irp in the stack at a time.
|
||
// Fail this secondary wait wake request.
|
||
//
|
||
status = STATUS_INVALID_DEVICE_STATE;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"Failing wait wake irp %p with %!STATUS! because wait wake irp "
|
||
"%p already pended",
|
||
Irp->GetIrp(), status, m_SharedPower.m_WaitWakeIrp);
|
||
}
|
||
else {
|
||
MdCancelRoutine pRoutine;
|
||
|
||
//
|
||
// No wait wake irp is currently in the stack, so attempt to set
|
||
// a cancel routine and transition the power state machine into a
|
||
// a state where it can arm the device at the bus level for this
|
||
// child.
|
||
//
|
||
// The power state machine expects the wait wake irp to have a cancel
|
||
// routine set in all states. For those states which require a
|
||
// non cancelable irp, those states clear the cancel routine as
|
||
// appropriate.
|
||
//
|
||
pRoutine = Irp->SetCancelRoutine(_PowerWaitWakeCancelRoutine);
|
||
#if DBG
|
||
ASSERT(pRoutine == NULL);
|
||
#else
|
||
UNREFERENCED_PARAMETER(pRoutine);
|
||
#endif
|
||
status = STATUS_PENDING;
|
||
|
||
if (Irp->IsCanceled()) {
|
||
DoTraceLevelMessage(GetDriverGlobals(),
|
||
TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"wait wake irp %p already canceled", Irp->GetIrp());
|
||
|
||
//
|
||
// This IRP has already been cancelled, we must clear the cancel
|
||
// routine before completing the IRP.
|
||
//
|
||
pRoutine = Irp->SetCancelRoutine(NULL);
|
||
|
||
if (pRoutine != NULL) {
|
||
//
|
||
// Our cancel routine will not be called
|
||
//
|
||
#if DBG
|
||
ASSERT(pRoutine == _PowerWaitWakeCancelRoutine);
|
||
#else
|
||
UNREFERENCED_PARAMETER(pRoutine);
|
||
#endif
|
||
Irp->SetStatus(STATUS_CANCELLED);
|
||
status = STATUS_CANCELLED;
|
||
}
|
||
}
|
||
|
||
if (status == STATUS_PENDING) {
|
||
//
|
||
// Either we successfully set the cancel routine or the irp
|
||
// was canceled and the cancel routine is about to run when
|
||
// we drop the lock. If the routine is about to run, we still
|
||
// need to setup m_SharedPower to values that it expects.
|
||
//
|
||
Irp->MarkIrpPending();
|
||
m_SharedPower.m_WaitWakeIrp = Irp->GetIrp();
|
||
}
|
||
}
|
||
m_PowerMachine.m_WaitWakeLock.Release(irql);
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
//
|
||
// Post to the appropriate matchines
|
||
//
|
||
PowerProcessEvent(PowerWakeArrival);
|
||
|
||
if (IsPowerPolicyOwner()) {
|
||
PowerPolicyProcessEvent(PwrPolWakeArrived);
|
||
}
|
||
}
|
||
else {
|
||
CompletePowerRequest(Irp, status);
|
||
}
|
||
|
||
return status;
|
||
}
|
||
else if (IsPowerPolicyOwner()) {
|
||
//
|
||
// Try to set m_WaitWakeIrp to the new IRP value only if the current
|
||
// value of m_WaitWakeIrp is NULL because there can only be one
|
||
// active wait wake irp in the stack at a time. Since the power policy
|
||
// state machine never sends more then one, this is really a guard
|
||
// against some other device in the stack sending a wait wake irp to
|
||
// this device in the wrong state.
|
||
//
|
||
oldIrp = (PIRP) InterlockedCompareExchangePointer(
|
||
(PVOID*) &m_SharedPower.m_WaitWakeIrp,
|
||
Irp->GetIrp(),
|
||
NULL
|
||
);
|
||
|
||
//
|
||
// If oldIrp is NULL then there was no previous irp and we successfully
|
||
// exchanged the new PIRP value into m_WaitWakeIrp
|
||
//
|
||
if (oldIrp == NULL) {
|
||
m_PowerPolicyMachine.SetWaitWakeUnclaimed();
|
||
|
||
//
|
||
// NOTE: There is a built in race condition here that WDF cannot
|
||
// solve with the given WDM primitives. After arming the
|
||
// device for wake, there is a window where the wait wake irp
|
||
// has not yet been processed by the wait wake owner. Until
|
||
// the wake request is processed, wake events could be generated
|
||
// and lost. There is nothing we can do about this until we
|
||
// have synchronous "goto Dx and arm" command available.
|
||
//
|
||
Irp->CopyCurrentIrpStackLocationToNext();
|
||
Irp->SetCompletionRoutineEx(m_Device->GetDeviceObject(),
|
||
_PowerPolicyWaitWakeCompletionRoutine,
|
||
this);
|
||
|
||
//
|
||
// Technically, there should be a PDO vs FDO overload here which
|
||
// does the right thing w/regard to this request and if it is at the
|
||
// bottom of the stack or not. But, by design, we check for
|
||
// m_WaitWakeOwner first which has the direct side affect of making
|
||
// it impossible for a PDO to get to this point.
|
||
//
|
||
ASSERT(m_Device->IsFdo());
|
||
|
||
status = Irp->PoCallDriver(m_Device->GetAttachedDevice());
|
||
|
||
//
|
||
// Send the wake arrived after sending the request as commented above.
|
||
// This window between sending the request and sending the event to
|
||
// the state machine allows the wait owner to complete the request
|
||
// immediately. When completed synchronously, it has an effect on
|
||
// both wake scenarios:
|
||
//
|
||
// 1) wake from S0: the device never transitions to Dx and the idle
|
||
// timer is resumed if no i/o is present
|
||
//
|
||
// 2) wake from sx: the device is disarmed for wake from Sx and
|
||
// put into Dx with being armed for wake.
|
||
//
|
||
PowerPolicyProcessEvent(PwrPolWakeArrived);
|
||
}
|
||
else {
|
||
status = STATUS_POWER_STATE_INVALID;
|
||
|
||
DoTraceLevelMessage(
|
||
FxDriverGlobals, TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"already have a ww irp %p, failing new ww irp %p with %!STATUS!",
|
||
oldIrp, Irp->GetIrp(), status);
|
||
|
||
CompletePowerRequest(Irp, status);
|
||
}
|
||
}
|
||
else {
|
||
//
|
||
// Not the power policy owner, not the wait wake irp owner, ignore the
|
||
// irp
|
||
//
|
||
// This will release the remove lock.
|
||
//
|
||
status = FireAndForgetIrp(Irp);
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::RegisterCallbacks(
|
||
__in PWDF_PNPPOWER_EVENT_CALLBACKS DispatchTable
|
||
)
|
||
{
|
||
NTSTATUS status;
|
||
|
||
//
|
||
// Update the callback table.
|
||
//
|
||
m_DeviceD0Entry.m_Method = DispatchTable->EvtDeviceD0Entry;
|
||
m_DeviceD0EntryPostInterruptsEnabled.m_Method =
|
||
DispatchTable->EvtDeviceD0EntryPostInterruptsEnabled;
|
||
m_DeviceD0ExitPreInterruptsDisabled.m_Method =
|
||
DispatchTable->EvtDeviceD0ExitPreInterruptsDisabled;
|
||
m_DeviceD0Exit.m_Method = DispatchTable->EvtDeviceD0Exit;
|
||
|
||
m_DevicePrepareHardware.m_Method = DispatchTable->EvtDevicePrepareHardware;
|
||
m_DeviceReleaseHardware.m_Method = DispatchTable->EvtDeviceReleaseHardware;
|
||
|
||
m_DeviceQueryStop.m_Method = DispatchTable->EvtDeviceQueryStop;
|
||
m_DeviceQueryRemove.m_Method = DispatchTable->EvtDeviceQueryRemove;
|
||
|
||
m_DeviceSurpriseRemoval.m_Method = DispatchTable->EvtDeviceSurpriseRemoval;
|
||
|
||
m_DeviceUsageNotification.m_Method = DispatchTable->EvtDeviceUsageNotification;
|
||
m_DeviceUsageNotificationEx.m_Method = DispatchTable->EvtDeviceUsageNotificationEx;
|
||
m_DeviceRelationsQuery.m_Method = DispatchTable->EvtDeviceRelationsQuery;
|
||
|
||
if (DispatchTable->EvtDeviceSelfManagedIoCleanup != NULL ||
|
||
DispatchTable->EvtDeviceSelfManagedIoFlush != NULL ||
|
||
DispatchTable->EvtDeviceSelfManagedIoInit != NULL ||
|
||
DispatchTable->EvtDeviceSelfManagedIoSuspend != NULL ||
|
||
DispatchTable->EvtDeviceSelfManagedIoRestart != NULL) {
|
||
|
||
status = FxSelfManagedIoMachine::_CreateAndInit(&m_SelfManagedIoMachine,
|
||
this);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
m_SelfManagedIoMachine->InitializeMachine(DispatchTable);
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::RegisterPowerPolicyCallbacks(
|
||
__in PWDF_POWER_POLICY_EVENT_CALLBACKS Callbacks
|
||
)
|
||
{
|
||
m_PowerPolicyMachine.m_Owner->m_DeviceArmWakeFromS0.m_Method =
|
||
Callbacks->EvtDeviceArmWakeFromS0;
|
||
m_PowerPolicyMachine.m_Owner->m_DeviceArmWakeFromSx.m_Method =
|
||
Callbacks->EvtDeviceArmWakeFromSx;
|
||
m_PowerPolicyMachine.m_Owner->m_DeviceArmWakeFromSx.m_MethodWithReason =
|
||
Callbacks->EvtDeviceArmWakeFromSxWithReason;
|
||
|
||
m_PowerPolicyMachine.m_Owner->m_DeviceDisarmWakeFromS0.m_Method =
|
||
Callbacks->EvtDeviceDisarmWakeFromS0;
|
||
m_PowerPolicyMachine.m_Owner->m_DeviceDisarmWakeFromSx.m_Method =
|
||
Callbacks->EvtDeviceDisarmWakeFromSx;
|
||
|
||
m_PowerPolicyMachine.m_Owner->m_DeviceWakeFromS0Triggered.m_Method =
|
||
Callbacks->EvtDeviceWakeFromS0Triggered;
|
||
m_PowerPolicyMachine.m_Owner->m_DeviceWakeFromSxTriggered.m_Method =
|
||
Callbacks->EvtDeviceWakeFromSxTriggered;
|
||
}
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::RegisterPowerPolicyWmiInstance(
|
||
__in const GUID* Guid,
|
||
__in FxWmiInstanceInternalCallbacks* Callbacks,
|
||
__out FxWmiInstanceInternal** Instance
|
||
)
|
||
{
|
||
WDF_WMI_PROVIDER_CONFIG config;
|
||
NTSTATUS status;
|
||
|
||
WDF_WMI_PROVIDER_CONFIG_INIT(&config, Guid);
|
||
|
||
//
|
||
// We are assuming we are registering either for the wait wake or device
|
||
// timeout GUIDs which both operate on BOOLEANs. If we expand this API in
|
||
// the future, have the caller pass in a config structure for the provider
|
||
// GUID.
|
||
//
|
||
config.MinInstanceBufferSize = sizeof(BOOLEAN);
|
||
|
||
status = m_Device->m_PkgWmi->AddPowerPolicyProviderAndInstance(
|
||
&config,
|
||
Callbacks,
|
||
Instance);
|
||
|
||
if (status == STATUS_OBJECT_NAME_COLLISION) {
|
||
status = STATUS_SUCCESS;
|
||
}
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"Failed to register WMI power GUID %!STATUS!", status);
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::PowerPolicySetS0IdleSettings(
|
||
__in PWDF_DEVICE_POWER_POLICY_IDLE_SETTINGS Settings
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Updates the S0 Idle settings for the device and then posts an update event
|
||
to the power policy state machine. The first this function is called, the
|
||
ability to allow the user to control this setting is set.
|
||
|
||
Arguments:
|
||
|
||
Settings - The settings to apply.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
{
|
||
DEVICE_POWER_STATE dxState;
|
||
ULONG idleTimeout;
|
||
NTSTATUS status;
|
||
BOOLEAN enabled, s0Capable, overridable, firstTime;
|
||
WDF_TRI_STATE powerUpOnSystemWake;
|
||
const LONGLONG negliblySmallIdleTimeout = -1; // 100 nanoseconds
|
||
|
||
s0Capable = FALSE;
|
||
dxState = PowerDeviceD3;
|
||
overridable = FALSE;
|
||
firstTime = TRUE;
|
||
|
||
if (Settings->Enabled == WdfTrue) {
|
||
enabled = TRUE;
|
||
|
||
} else if (Settings->Enabled == WdfUseDefault) {
|
||
enabled = TRUE;
|
||
|
||
if (Mx::MxGetCurrentIrql() == PASSIVE_LEVEL) {
|
||
DECLARE_CONST_UNICODE_STRING(valueName, WDF_S0_IDLE_DEFAULT_VALUE_NAME);
|
||
|
||
//
|
||
// Read registry. If registry value is not found, the value of "enabled"
|
||
// remains unchanged
|
||
//
|
||
ReadRegistryS0Idle(&valueName, &enabled);
|
||
}
|
||
else {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_WARNING, TRACINGPNP,
|
||
"If registry value WdfDefaultIdleInWorkingState was present, "
|
||
"it was not read because DDI WdfDeviceAssignS0IdleSettings "
|
||
"was not called at PASSIVE_LEVEL");
|
||
}
|
||
}
|
||
else {
|
||
enabled = FALSE;
|
||
}
|
||
|
||
if (m_PowerPolicyMachine.m_Owner->m_IdleSettings.Set) {
|
||
firstTime = FALSE;
|
||
}
|
||
|
||
if (m_CapsQueried == FALSE && Mx::MxGetCurrentIrql() == PASSIVE_LEVEL) {
|
||
status = QueryForCapabilities();
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Do not set m_CapsQueried to TRUE yet because we will do that once we
|
||
// know the entire stack has been built we will do the query again.
|
||
//
|
||
}
|
||
|
||
switch (Settings->IdleCaps) {
|
||
case IdleUsbSelectiveSuspend:
|
||
case IdleCanWakeFromS0:
|
||
s0Capable = TRUE;
|
||
|
||
if (Settings->DxState == PowerDeviceMaximum) {
|
||
dxState = PowerPolicyGetDeviceDeepestDeviceWakeState(PowerSystemWorking);
|
||
|
||
//
|
||
// Some bus drivers
|
||
|
||
// incorrectly report DeviceWake=D0 to
|
||
// indicate that it does not support wake instead of specifying
|
||
// PowerDeviceUnspecified and KMDF ends up requesting
|
||
// a D0 irp when going to Dx. The check prevents this bug.
|
||
//
|
||
if (dxState < PowerDeviceD1 ||
|
||
dxState > PowerDeviceD3 ||
|
||
(dxState > PowerDeviceD2 && Settings->IdleCaps == IdleUsbSelectiveSuspend)
|
||
) {
|
||
status = STATUS_POWER_STATE_INVALID;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"DeviceWake power state reported in device capabilities "
|
||
"%!DEVICE_POWER_STATE! indicates that device can not signal"
|
||
" a wake event, %!STATUS!",
|
||
dxState, status);
|
||
return status;
|
||
}
|
||
}
|
||
else {
|
||
DEVICE_POWER_STATE dxDeepest;
|
||
|
||
dxState = Settings->DxState;
|
||
dxDeepest = PowerPolicyGetDeviceDeepestDeviceWakeState(PowerSystemWorking);
|
||
|
||
if (dxState > dxDeepest) {
|
||
status = STATUS_POWER_STATE_INVALID;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"DxState specified by driver %!DEVICE_POWER_STATE! cannot "
|
||
"be lighter than lightest available device wake state"
|
||
" %!DEVICE_POWER_STATE!, %!STATUS!", dxState,
|
||
dxDeepest, status);
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Can only perform wait wake from D2 on a USB device
|
||
//
|
||
if (dxState > PowerDeviceD2 &&
|
||
Settings->IdleCaps == IdleUsbSelectiveSuspend) {
|
||
status = STATUS_POWER_STATE_INVALID;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"DxState specified by driver %!DEVICE_POWER_STATE! cannot "
|
||
"be lighter than PowerDeviceD2 for USB selective suspend "
|
||
"%!STATUS!",
|
||
dxState, status);
|
||
return status;
|
||
}
|
||
}
|
||
|
||
if (Settings->IdleCaps == IdleUsbSelectiveSuspend) {
|
||
status = m_PowerPolicyMachine.InitUsbSS();
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"Failed to initialize USB selective suspend %!STATUS!",
|
||
status);
|
||
return status;
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
case IdleCannotWakeFromS0:
|
||
s0Capable = FALSE;
|
||
|
||
if (Settings->DxState == PowerDeviceMaximum) {
|
||
dxState = PowerDeviceD3;
|
||
}
|
||
else {
|
||
dxState = Settings->DxState;
|
||
}
|
||
|
||
break;
|
||
|
||
default:
|
||
ASSERT(FALSE);
|
||
break;
|
||
}
|
||
|
||
if (Settings->IdleTimeout == IdleTimeoutDefaultValue) {
|
||
idleTimeout = FxPowerPolicyDefaultTimeout;
|
||
}
|
||
else {
|
||
idleTimeout = Settings->IdleTimeout;
|
||
}
|
||
|
||
if (Settings->UserControlOfIdleSettings == IdleAllowUserControl) {
|
||
|
||
status = UpdateWmiInstanceForS0Idle(AddInstance);
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
if (Settings->Enabled == WdfUseDefault) {
|
||
//
|
||
// Read the registry entry for idle enabled if it's the first time.
|
||
//
|
||
if (firstTime && Mx::MxGetCurrentIrql() == PASSIVE_LEVEL) {
|
||
DECLARE_CONST_UNICODE_STRING(valueName, WDF_S0_IDLE_ENABLED_VALUE_NAME);
|
||
|
||
//
|
||
// Read registry. If registry value is not found, the value of
|
||
// "enabled" remains unchanged
|
||
//
|
||
ReadRegistryS0Idle(&valueName, &enabled);
|
||
}
|
||
else {
|
||
//
|
||
// Use the saved value for idle enabled.
|
||
//
|
||
enabled = m_PowerPolicyMachine.m_Owner->m_IdleSettings.Enabled;
|
||
}
|
||
}
|
||
|
||
overridable = TRUE;
|
||
}
|
||
else if (Settings->UserControlOfIdleSettings == IdleDoNotAllowUserControl) {
|
||
//
|
||
// No user control
|
||
//
|
||
overridable = FALSE;
|
||
|
||
(void) UpdateWmiInstanceForS0Idle(RemoveInstance);
|
||
}
|
||
|
||
//
|
||
// !!!! DO NOT INTRODUCE FAILURES BEYOND THIS POINT !!!!
|
||
//
|
||
// We should not introduce any failures that are not due to driver errors
|
||
// beyond this point. This is because we are going to commit the driver's
|
||
// S0-idle settings now and any failure in the midst of that could leave us
|
||
// in a bad state. Therefore, all failable code where the failure is beyond
|
||
// the driver's control should be placed above this point.
|
||
//
|
||
// For example, a driver may want wake-from-S0 support, but the device may
|
||
// not support it. We already checked for that failure above, before we
|
||
// started committing any of the driver's S0-idle settings.
|
||
//
|
||
// Any failures below this point should only be due to driver errors, i.e.
|
||
// the driver incorrectly calling the AssignS0IdleSettings DDI.
|
||
//
|
||
|
||
if (firstTime) {
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.Set = TRUE;
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.Overridable = overridable;
|
||
}
|
||
|
||
//
|
||
// IdleTimeoutType is available only on > 1.9
|
||
//
|
||
if (Settings->Size > sizeof(WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_V1_9)) {
|
||
if (firstTime) {
|
||
if ((SystemManagedIdleTimeout == Settings->IdleTimeoutType) ||
|
||
(SystemManagedIdleTimeoutWithHint ==
|
||
Settings->IdleTimeoutType)) {
|
||
//
|
||
// This is the first time S0-idle policy is being specified and
|
||
// the caller has asked for the idle timeout to be determined
|
||
// by the power manager.
|
||
//
|
||
status = m_PowerPolicyMachine.m_Owner->m_IdleSettings.
|
||
m_TimeoutMgmt.UseSystemManagedIdleTimeout(
|
||
GetDriverGlobals()
|
||
);
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
}
|
||
} else {
|
||
//
|
||
// This is not the first time S0-idle policy is being specified.
|
||
// Verify that the caller is not trying to change their mind about
|
||
// whether the idle timeout is determined by the power manager.
|
||
//
|
||
BOOLEAN currentlyUsingSystemManagedIdleTimeout;
|
||
BOOLEAN callerWantsSystemManagedIdleTimeout;
|
||
|
||
currentlyUsingSystemManagedIdleTimeout =
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.m_TimeoutMgmt.
|
||
UsingSystemManagedIdleTimeout();
|
||
callerWantsSystemManagedIdleTimeout =
|
||
((SystemManagedIdleTimeout == Settings->IdleTimeoutType) ||
|
||
(SystemManagedIdleTimeoutWithHint == Settings->IdleTimeoutType));
|
||
|
||
//
|
||
// UMDF currently does not implement
|
||
// IdleTimeoutManagement::_SystemManagedIdleTimeoutAvailable. So
|
||
// that method must be called only as part of the second check in
|
||
// the "if" statement below. Since UMDF currently does not support
|
||
// system managed idle timeout, the first check will always evaluate
|
||
// to 'FALSE', so the second check never gets executed for UMDF.
|
||
//
|
||
if ((callerWantsSystemManagedIdleTimeout !=
|
||
currentlyUsingSystemManagedIdleTimeout)
|
||
&&
|
||
(IdleTimeoutManagement::_SystemManagedIdleTimeoutAvailable())
|
||
) {
|
||
|
||
status = STATUS_INVALID_DEVICE_REQUEST;
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"A previous call to assign S0-idle policy specified that "
|
||
"the idle timeout %s be determined by the power manager. "
|
||
"This decision cannot be changed. %!STATUS!",
|
||
currentlyUsingSystemManagedIdleTimeout ?
|
||
"should" :
|
||
"should not",
|
||
status);
|
||
FxVerifierDbgBreakPoint(GetDriverGlobals());
|
||
return status;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (Settings->IdleCaps == IdleCannotWakeFromS0) {
|
||
//
|
||
// PowerUpIdleDeviceOnSystemWake field added after v1.7.
|
||
// By default KMDF uses an optimization where the device is not powered
|
||
// up when resuming from Sx if it is idle. The field
|
||
// PowerUpIdleDeviceOnSystemWake is used to turn off this optimization and allow
|
||
// device to power up when resuming from Sx. Note that this optimization
|
||
// is applicable only for IdleCannotWakeFromS0. In other cases the
|
||
// device is always powered up in order to arm for wake.
|
||
//
|
||
powerUpOnSystemWake =
|
||
(Settings->Size > sizeof(WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_V1_7)) ?
|
||
Settings->PowerUpIdleDeviceOnSystemWake :
|
||
WdfUseDefault;
|
||
|
||
switch(powerUpOnSystemWake) {
|
||
case WdfTrue:
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.PowerUpIdleDeviceOnSystemWake = TRUE;
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"Driver turned off S0Idle optimization. Device will be "
|
||
"powered up on resume from Sx even when it is idle");
|
||
break;
|
||
case WdfFalse:
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.PowerUpIdleDeviceOnSystemWake = FALSE;
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"Driver turned on S0Idle optimization. Device will remain "
|
||
"powered off if idle when resuming from Sx");
|
||
break;
|
||
case WdfUseDefault:
|
||
DO_NOTHING();
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (FALSE ==
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.UsbSSCapabilityKnown)
|
||
{
|
||
if (Settings->IdleCaps == IdleUsbSelectiveSuspend) {
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.UsbSSCapable = TRUE;
|
||
m_PowerPolicyMachine.m_Owner->
|
||
m_IdleSettings.UsbSSCapabilityKnown = TRUE;
|
||
|
||
} else if (Settings->IdleCaps == IdleCanWakeFromS0) {
|
||
|
||
m_PowerPolicyMachine.m_Owner->
|
||
m_IdleSettings.UsbSSCapabilityKnown = TRUE;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Wake FromS0Capable is set every time because we want to allow the driver
|
||
// to swap between idle wake capable and idle not wake capable. This should
|
||
// be allowed so that a scenario similar to the following can be implemented:
|
||
//
|
||
// a) when the device has an outstanding open, the device should arm itself
|
||
// for wake when idle
|
||
//
|
||
// b) when the device does not have an outstanding open, the device should
|
||
// be off and not armed.
|
||
//
|
||
// The only way to be off is to assign S0 wake settings, so the
|
||
// WakeFromS0Capable field must change on each DDI call. This is not a
|
||
// problem for the power policy state machine because it evaluates
|
||
// WakeFromS0Capable state before enabling of idle. If we are not
|
||
// WakeFromS0Capable and USB SS capable (ie a have a torn/unsynchronized
|
||
// state) in m_IdleSettings, we will recover from it when processing
|
||
// PwrPolS0IdlePolicyChanged in the state machine (b/c this event causes
|
||
// both fields to be reevaluated).
|
||
//
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.WakeFromS0Capable = s0Capable;
|
||
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.DxState = dxState;
|
||
|
||
if (m_PowerPolicyMachine.m_Owner->
|
||
m_IdleSettings.m_TimeoutMgmt.UsingSystemManagedIdleTimeout()) {
|
||
//
|
||
// With system managed idle timeout, we don't want to apply an idle
|
||
// timeout of our own on top of that. Effectively, our idle timeout is
|
||
// 0.
|
||
// But we apply a negligibly small timeout value as this allows us to
|
||
// keep the same logic in the idle state machine, regardless of whether
|
||
// we're using system-managed idle timeout or driver-managed idle
|
||
// timeout.
|
||
//
|
||
if (firstTime) {
|
||
m_PowerPolicyMachine.m_Owner->
|
||
m_PowerIdleMachine.m_PowerTimeout.QuadPart =
|
||
negliblySmallIdleTimeout;
|
||
}
|
||
|
||
if (SystemManagedIdleTimeoutWithHint == Settings->IdleTimeoutType) {
|
||
//
|
||
// We save the idle timeout hint, but we don't provide the hint to
|
||
// the power framework immediately. This is because currently we may
|
||
// or may not be registered with the power framework. Note that
|
||
// WdfDeviceAssignS0IdleSettings might get called even when we are
|
||
// not registered with the power framework.
|
||
//
|
||
// Therefore, we provide the hint to the power framework only when
|
||
// we get to the WdfDevStatePwrPolStartingDecideS0Wake state. This
|
||
// state is a good choice for providing the hint because:
|
||
// 1. We know we would be registered with the power framework when
|
||
// we are in this state.
|
||
// 2. Any change in S0-idle settings causes us to go through this
|
||
// state.
|
||
//
|
||
m_PowerPolicyMachine.m_Owner->m_PoxInterface.m_NextIdleTimeoutHint =
|
||
idleTimeout;
|
||
}
|
||
|
||
} else {
|
||
m_PowerPolicyMachine.m_Owner->m_PowerIdleMachine.m_PowerTimeout.QuadPart
|
||
= WDF_REL_TIMEOUT_IN_MS(idleTimeout);
|
||
}
|
||
|
||
//
|
||
// If the driver is 1.11 or later, update the bus drivers with the client's
|
||
// choice on the topic of D3hot or D3cold.
|
||
//
|
||
if ((Settings->Size > sizeof(WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_V1_9)) &&
|
||
(Settings->ExcludeD3Cold != WdfUseDefault)) {
|
||
MxDeviceObject deviceObject;
|
||
BOOLEAN enableD3Cold;
|
||
|
||
deviceObject.SetObject(m_Device->GetDeviceObject());
|
||
|
||
switch (Settings->ExcludeD3Cold) {
|
||
case WdfFalse:
|
||
enableD3Cold = TRUE;
|
||
break;
|
||
default:
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"Invalid tri-state value for ExcludeD3Cold %d",
|
||
Settings->ExcludeD3Cold);
|
||
__fallthrough;
|
||
case WdfTrue:
|
||
enableD3Cold = FALSE;
|
||
break;
|
||
}
|
||
|
||
SetD3ColdSupport(GetDriverGlobals(),
|
||
&deviceObject,
|
||
&m_D3ColdInterface,
|
||
enableD3Cold);
|
||
}
|
||
|
||
PowerPolicySetS0IdleState(enabled);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::PowerPolicySetSxWakeSettings(
|
||
__in PWDF_DEVICE_POWER_POLICY_WAKE_SETTINGS Settings,
|
||
__in BOOLEAN ArmForWakeIfChildrenAreArmedForWake,
|
||
__in BOOLEAN IndicateChildWakeOnParentWake
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Updates the Sx wake settings for the device. No event is posted to the
|
||
state machine because this setting is statically checked when the machine
|
||
is entering an Sx state (unlike S0 idle which can be checked at any time).
|
||
|
||
The first this function is called, the ability to allow the user to control
|
||
this setting is set.
|
||
|
||
Arguments:
|
||
|
||
Settings - the new settings to apply
|
||
|
||
ArmForWakeIfChildrenAreArmedForWake - Inidicates whether the device
|
||
should arm for wake when one or more children are armed for wake
|
||
|
||
IndicateChildWakeOnParentWake - Indicates whether the device should
|
||
propagate the wake status to its children
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
--*/
|
||
{
|
||
DEVICE_POWER_STATE dxState;
|
||
NTSTATUS status;
|
||
BOOLEAN overridable, firstTime, enabled;
|
||
|
||
dxState = PowerDeviceD3;
|
||
overridable = FALSE;
|
||
firstTime = TRUE;
|
||
|
||
if (Settings->Enabled == WdfTrue) {
|
||
enabled = TRUE;
|
||
|
||
}
|
||
else if (Settings->Enabled == WdfUseDefault) {
|
||
enabled = TRUE;
|
||
|
||
if (Mx::MxGetCurrentIrql() == PASSIVE_LEVEL) {
|
||
DECLARE_CONST_UNICODE_STRING(valueName, WDF_SX_WAKE_DEFAULT_VALUE_NAME);
|
||
|
||
//
|
||
// Read registry. If registry value is not found, the value of "enabled"
|
||
// remains unchanged
|
||
//
|
||
ReadRegistrySxWake(&valueName, &enabled);
|
||
}
|
||
else {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_WARNING, TRACINGPNP,
|
||
"If registry value WdfDefaultWakeFromSleepState was present, "
|
||
"it was not read because DDI WdfDeviceAssignSxWakeSettings "
|
||
"was not called at PASSIVE_LEVEL");
|
||
}
|
||
}
|
||
else {
|
||
enabled = FALSE;
|
||
}
|
||
|
||
if (m_PowerPolicyMachine.m_Owner->m_WakeSettings.Set) {
|
||
firstTime = FALSE;
|
||
}
|
||
|
||
if (m_CapsQueried == FALSE && Mx::MxGetCurrentIrql() == PASSIVE_LEVEL) {
|
||
status = QueryForCapabilities();
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Do not set m_CapsQueried to TRUE yet because we will do that once we
|
||
// know the entire stack has been built we will do the query again.
|
||
//
|
||
}
|
||
|
||
if (Settings->DxState == PowerDeviceMaximum) {
|
||
dxState = PowerPolicyGetDeviceDeepestDeviceWakeState((SYSTEM_POWER_STATE)m_SystemWake);
|
||
|
||
//
|
||
// Some bus drivers
|
||
|
||
// incorrectly report DeviceWake=D0 to
|
||
// indicate that it does not support wake instead of specifying
|
||
// PowerDeviceUnspecified and KMDF ends up requesting
|
||
// a D0 irp when going to Dx. The check prevents this bug.
|
||
//
|
||
if (dxState < PowerDeviceD1 || dxState > PowerDeviceD3) {
|
||
status = STATUS_POWER_STATE_INVALID;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"DeviceWake power state reported in device capabilities "
|
||
"%!DEVICE_POWER_STATE! indicates that device can not signal a "
|
||
"wake event, %!STATUS!",
|
||
dxState, status);
|
||
return status;
|
||
}
|
||
}
|
||
else {
|
||
DEVICE_POWER_STATE dxDeepest;
|
||
|
||
dxState = Settings->DxState;
|
||
dxDeepest = PowerPolicyGetDeviceDeepestDeviceWakeState((SYSTEM_POWER_STATE)m_SystemWake);
|
||
|
||
if (dxState > dxDeepest) {
|
||
status = STATUS_POWER_STATE_INVALID;
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"DxState specified by driver %!DEVICE_POWER_STATE! cannot be"
|
||
" lighter than lightest available device wake state "
|
||
"%!DEVICE_POWER_STATE!, %!STATUS!", dxState,
|
||
dxDeepest, status);
|
||
return status;
|
||
}
|
||
}
|
||
|
||
if (Settings->UserControlOfWakeSettings == WakeAllowUserControl) {
|
||
|
||
status = UpdateWmiInstanceForSxWake(AddInstance);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
if (Settings->Enabled == WdfUseDefault) {
|
||
//
|
||
// Read the registry entry for wake enabled if it's the first time.
|
||
//
|
||
if (firstTime && Mx::MxGetCurrentIrql() == PASSIVE_LEVEL) {
|
||
DECLARE_CONST_UNICODE_STRING(valueName, WDF_SX_WAKE_ENABLED_VALUE_NAME);
|
||
|
||
//
|
||
// Read registry. If registry value is not found, the value of
|
||
// "enabled" remains unchanged
|
||
//
|
||
ReadRegistrySxWake(&valueName, &enabled);
|
||
}
|
||
else {
|
||
//
|
||
// Use the saved value for wake enabled.
|
||
//
|
||
enabled = m_PowerPolicyMachine.m_Owner->m_WakeSettings.Enabled;
|
||
}
|
||
}
|
||
|
||
overridable = TRUE;
|
||
}
|
||
else if (Settings->UserControlOfWakeSettings == WakeDoNotAllowUserControl) {
|
||
//
|
||
// No user control, just set to enabled
|
||
//
|
||
overridable = FALSE;
|
||
|
||
(void) UpdateWmiInstanceForSxWake(RemoveInstance);
|
||
}
|
||
|
||
if (firstTime) {
|
||
m_PowerPolicyMachine.m_Owner->m_WakeSettings.Set = TRUE;
|
||
m_PowerPolicyMachine.m_Owner->m_WakeSettings.Overridable = overridable;
|
||
|
||
//
|
||
// If ArmForWakeIfChildrenAreArmedForWake setting is set to FALSE,
|
||
// then we use the legacy framework behavior which did not depend
|
||
// on the child device being capable of arming for wake or not.
|
||
//
|
||
m_PowerPolicyMachine.m_Owner->m_WakeSettings.ArmForWakeIfChildrenAreArmedForWake =
|
||
ArmForWakeIfChildrenAreArmedForWake;
|
||
|
||
//
|
||
// If IndicateChildWakeOnParentWake setting is set to FALSE, then
|
||
// we use the legacy framework behavior wherein the wake status
|
||
// is not propagated from the parent device to the child device.
|
||
//
|
||
m_PowerPolicyMachine.m_Owner->m_WakeSettings.IndicateChildWakeOnParentWake =
|
||
IndicateChildWakeOnParentWake;
|
||
}
|
||
|
||
m_PowerPolicyMachine.m_Owner->m_WakeSettings.DxState = dxState;
|
||
|
||
PowerPolicySetSxWakeState(enabled);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::_S0IdleQueryInstance(
|
||
__in CfxDevice* Device,
|
||
__in FxWmiInstanceInternal* /* Instance */,
|
||
__in ULONG /* OutBufferSize */,
|
||
__out PVOID OutBuffer,
|
||
__out PULONG BufferUsed
|
||
)
|
||
{
|
||
*((BOOLEAN*) OutBuffer) =
|
||
(Device->m_PkgPnp)->m_PowerPolicyMachine.m_Owner->m_IdleSettings.Enabled;
|
||
*BufferUsed = sizeof(BOOLEAN);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::_S0IdleSetInstance(
|
||
__in CfxDevice* Device,
|
||
__in FxWmiInstanceInternal* /* Instance */,
|
||
__in ULONG /* InBufferSize */,
|
||
__in PVOID InBuffer
|
||
)
|
||
{
|
||
BOOLEAN value;
|
||
|
||
|
||
//
|
||
// FxWmiIrpHandler makes sure the buffer is at least one byte big, so we
|
||
// don't check the buffer size.
|
||
//
|
||
|
||
value = *(PBOOLEAN) InBuffer;
|
||
|
||
(Device->m_PkgPnp)->PowerPolicySetS0IdleState(value);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::_S0IdleSetItem(
|
||
__in CfxDevice* Device,
|
||
__in FxWmiInstanceInternal* /* Instance */,
|
||
__in ULONG DataItemId,
|
||
__in ULONG InBufferSize,
|
||
__in PVOID InBuffer
|
||
)
|
||
{
|
||
BOOLEAN value;
|
||
|
||
if (DataItemId != 0) {
|
||
return STATUS_INVALID_DEVICE_REQUEST;
|
||
}
|
||
|
||
if (InBufferSize < sizeof(BOOLEAN)) {
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
value = *(BOOLEAN*) InBuffer;
|
||
(Device->m_PkgPnp)->PowerPolicySetS0IdleState(value);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::_SxWakeQueryInstance(
|
||
__in CfxDevice* Device,
|
||
__in FxWmiInstanceInternal* /* Instance */,
|
||
__in ULONG /* OutBufferSize */,
|
||
__out PVOID OutBuffer,
|
||
__out PULONG BufferUsed
|
||
)
|
||
{
|
||
*((BOOLEAN*) OutBuffer) =
|
||
(Device->m_PkgPnp)->m_PowerPolicyMachine.m_Owner->m_WakeSettings.Enabled;
|
||
*BufferUsed = sizeof(BOOLEAN);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::_SxWakeSetInstance(
|
||
__in CfxDevice* Device,
|
||
__in FxWmiInstanceInternal* /* Instance */,
|
||
__in ULONG /* InBufferSize */,
|
||
__in PVOID InBuffer
|
||
)
|
||
{
|
||
BOOLEAN value;
|
||
|
||
//
|
||
// FxWmiIrpHandler makes sure that the buffer is at least one byte big, so
|
||
// we don't check the buffer size
|
||
//
|
||
value = *(PBOOLEAN) InBuffer;
|
||
|
||
(Device->m_PkgPnp)->PowerPolicySetSxWakeState(value);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::_SxWakeSetItem(
|
||
__in CfxDevice* Device,
|
||
__in FxWmiInstanceInternal* /* Instance */,
|
||
__in ULONG DataItemId,
|
||
__in ULONG InBufferSize,
|
||
__in PVOID InBuffer
|
||
)
|
||
{
|
||
BOOLEAN value;
|
||
|
||
if (DataItemId != 0) {
|
||
return STATUS_INVALID_DEVICE_REQUEST;
|
||
}
|
||
|
||
if (InBufferSize < sizeof(BOOLEAN)) {
|
||
return STATUS_BUFFER_TOO_SMALL;
|
||
}
|
||
|
||
value = *(BOOLEAN*) InBuffer;
|
||
(Device->m_PkgPnp)->PowerPolicySetSxWakeState(value);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::PowerPolicyHandleSystemQueryPower(
|
||
__in SYSTEM_POWER_STATE QueryState
|
||
)
|
||
/*++
|
||
|
||
Framework Philosophy Discussion:
|
||
|
||
WDM sends IRP_MN_QUERY_POWER for system power states (where system power
|
||
states are S0-working, S1-light-sleep, S2-deeper-sleep, S3-deepest-sleep,
|
||
S4-hibernation, S5-soft-off.) The idea is that, if a driver can't support
|
||
a particular state, it fails the query. The problem is that this idea is
|
||
horribly broken.
|
||
|
||
The first problem is that WDM doesn't always send these IRPs. In some
|
||
situations, (very low battery, system getting critically hot) WDM will
|
||
attempt to preserve user data by sleeping or hibernating, rather than just
|
||
crashing. This is good, but it makes a driver writer's life very difficult,
|
||
since it means that you have to deal with being told to go to a particular
|
||
state even if you think that your device won't be able to deal well with
|
||
that state.
|
||
|
||
The second problem is that, by the time the system is going to sleep, the
|
||
user probably isn't still looking at the screen. This is especially true
|
||
for laptop computers, as the system is very likely sleeping because the
|
||
user closed the clamshell lid. So any attempt to ask the user how to
|
||
resolve a situation where a driver doesn't want to go to a low power state
|
||
is futile. Furthermore, even when the screen is still available, users
|
||
dislike it when they push the sleep button or the power button on their
|
||
machines and the machines don't do what they were told to do.
|
||
|
||
The third problem is related to the second. While there may be completely
|
||
legitimate reasons for the driver to want to delay or even to veto a
|
||
transition into a sleep state, (an example of a valid scenario would be one
|
||
in which the driver was involved in burning a CD, an operation which can't
|
||
be interrupted,) there isn't any good way for a driver to interact with a
|
||
user anyhow. (Which desktop is the right one to send messages to? What
|
||
should the UI for problem resolution look like? How does a driver put up
|
||
UI anyhow?)
|
||
|
||
All the driver really knows is that it will or won't be able to maintain
|
||
device state, and it will or won't be able to get enough power to arm
|
||
any wake events that it might want to deliver (like PME#.)
|
||
|
||
Consequently, the designers of the PnP/Power model in the Framework have
|
||
decided that all QueryPower-Sx IRPs will be completed successfully
|
||
except if the device cannot maintain enough power to trigger its wake
|
||
signal *AND* if the system supports lighter sleep states than the
|
||
one that is currently being queried. (If it does, then the kernel's power
|
||
manager will turn right around and query for those, next.)
|
||
|
||
This story usually brings up a few objections:
|
||
|
||
1) My device is important! When it's operating, I don't want
|
||
the machine to just fall asleep. I need to fail QueryPower-Sx to
|
||
prevent that.
|
||
|
||
This objection is an unfortunate consequence of the existing DDK. There
|
||
is a perfectly good API that allows a driver to say that the machine
|
||
shouldn't just fall asleep. (See PoSetSystemState.) If a user presses
|
||
a button telling the machine to go to sleep, then the driver has a
|
||
responsibility to do that.
|
||
|
||
2) There are certain operations that just can't be interrupted!
|
||
|
||
While that's true, those operations started somewhere, probably in user-
|
||
mode. Those same user-mode components would be much, much better suited
|
||
toward negotiating with the user or with other components to figure out
|
||
what to do when the uninterruptable must be interrupted. User-mode
|
||
components get notification that the system is going to sleep and they
|
||
can delay or veto the transition. Get over the idea that your driver
|
||
needs to be involved, too.
|
||
|
||
Routine Description:
|
||
|
||
Determines if for the passed in System state, if we can wake the machine
|
||
from it. If the query state is the machine's minimum system state, then
|
||
we always succeed it because we want the machine to go to at least some
|
||
sleeping state. We always succeed hibernate and system off requests as well.
|
||
|
||
Arguments:
|
||
|
||
QueryState - The proposed system state
|
||
|
||
Return Value:
|
||
|
||
NT_SUCCESS if the queried state should be allowed, !NT_SUCCESS otherwise
|
||
|
||
--*/
|
||
{
|
||
PFX_DRIVER_GLOBALS FxDriverGlobals = GetDriverGlobals();
|
||
NTSTATUS status;
|
||
|
||
if (QueryState >= PowerSystemHibernate ||
|
||
PowerPolicyCanWakeFromSystemState(QueryState)) {
|
||
//
|
||
// If the query is for the machine's minimum S state or we going into
|
||
// hibernate or off, always succeed it.
|
||
//
|
||
status = STATUS_SUCCESS;
|
||
}
|
||
else {
|
||
|
||
//
|
||
// On Windows Vista and above, its OK to return a failure status code
|
||
// if the system is going into an S state at which the device cannot
|
||
// wake the system.
|
||
//
|
||
ASSERT(FxLibraryGlobals.OsVersionInfo.dwMajorVersion >= 6);
|
||
|
||
//
|
||
// The S state the machine is going into is one where we can't
|
||
// wake it up because our D state is too low for this S state.
|
||
// Since this isn't the minimum S state the machine is capable
|
||
// of, reject the current query.
|
||
//
|
||
DoTraceLevelMessage(
|
||
FxDriverGlobals, TRACE_LEVEL_WARNING, TRACINGPNP,
|
||
"failing system query power because the device cannot wake the "
|
||
"machine from S%d",
|
||
QueryState - 1);
|
||
|
||
status = STATUS_POWER_STATE_INVALID;
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::PowerPolicySetS0IdleState(
|
||
__in BOOLEAN State
|
||
)
|
||
{
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.Enabled = State ? TRUE : FALSE;
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.Dirty = TRUE;
|
||
PowerPolicyProcessEvent(PwrPolS0IdlePolicyChanged);
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::PowerPolicySetSxWakeState(
|
||
__in BOOLEAN State
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Sets the wake from Sx state
|
||
|
||
No need to post an event to the power policy state machine because we
|
||
will not change any active due to a change in this setting. We only
|
||
evaluate this state when going into Sx and once in this state we will not
|
||
change our behavior until the next Sx, which will then evaluate this state.
|
||
|
||
Arguments:
|
||
State - New state
|
||
|
||
Return Value:
|
||
VOID
|
||
|
||
--*/
|
||
{
|
||
m_PowerPolicyMachine.m_Owner->m_WakeSettings.Enabled = State ? TRUE : FALSE;
|
||
m_PowerPolicyMachine.m_Owner->m_WakeSettings.Dirty = TRUE;
|
||
|
||
//
|
||
// Since we are not posting an event to the power policy state machine, try
|
||
// to write out the value now, otherwise it will be written when we
|
||
// transition
|
||
//
|
||
if (Mx::MxGetCurrentIrql() == PASSIVE_LEVEL) {
|
||
NTSTATUS status;
|
||
LONGLONG timeout;
|
||
|
||
timeout = 0;
|
||
|
||
//
|
||
// If the lock is already acquired on this thread, this will fail, which
|
||
// is OK.
|
||
//
|
||
status = m_PowerPolicyMachine.m_StateMachineLock.AcquireLock(
|
||
GetDriverGlobals(),
|
||
&timeout
|
||
);
|
||
|
||
if (FxWaitLockInternal::IsLockAcquired(status)) {
|
||
SaveState(TRUE);
|
||
|
||
m_PowerPolicyMachine.m_StateMachineLock.ReleaseLock(
|
||
GetDriverGlobals()
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::SetDeviceFailed(
|
||
__in WDF_DEVICE_FAILED_ACTION FailedAction
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Marks the device as a victim of catastrophic failure, either in software
|
||
or in hardware.
|
||
|
||
If AttemptToRestart is TRUE, then we should try to get the stack re-built
|
||
after it has been torn down. This would typically be the case the failure
|
||
was in the software, and possibly not be the case if the failure was in
|
||
the hardware.
|
||
|
||
Arguments:
|
||
FailedAction - action to take once the stack has been removed
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS status;
|
||
MdDeviceObject pdo;
|
||
|
||
#if (FX_CORE_MODE == FX_CORE_USER_MODE)
|
||
if (GetDriverGlobals()->IsVersionGreaterThanOrEqualTo(2, 15) == FALSE &&
|
||
FailedAction == WdfDeviceFailedAttemptRestart) {
|
||
|
||
FailedAction = WdfDeviceFailedNoRestart;
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_WARNING, TRACINGDEVICE,
|
||
"WdfDeviceFailedAttemptRestart is only available for UMDF 2.15 "
|
||
"and later drivers. Reverting to WdfDeviceFailedNoRestart.");
|
||
}
|
||
#endif
|
||
|
||
m_FailedAction = (BYTE) FailedAction;
|
||
|
||
//
|
||
// This will cause the PnP manager to tear down this stack, even if
|
||
// the PDO can't be surprise-removed.
|
||
//
|
||
m_Failed = TRUE;
|
||
|
||
if (FailedAction == WdfDeviceFailedAttemptRestart) {
|
||
//
|
||
// Attempt to get the PDO surprise-removed.
|
||
//
|
||
status = AskParentToRemoveAndReenumerate();
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
#if (FX_CORE_MODE == FX_CORE_KERNEL_MODE)
|
||
//
|
||
// In between creating a PDO WDFDEVICE and it starting, if this DDI is called,
|
||
// we will not have a valid PDO. Make sure it is valid before we proceed.
|
||
//
|
||
pdo = m_Device->GetSafePhysicalDevice();
|
||
|
||
if (pdo != NULL) {
|
||
//
|
||
// Now tell the PnP manager to re-query us for our state.
|
||
//
|
||
MxDeviceObject physicalDeviceObject(pdo);
|
||
|
||
physicalDeviceObject.InvalidateDeviceState(
|
||
m_Device->GetDeviceObject()
|
||
);
|
||
}
|
||
#else // USER_MODE
|
||
m_Device->GetMxDeviceObject()->InvalidateDeviceState(
|
||
m_Device->GetDeviceObject());
|
||
UNREFERENCED_PARAMETER(pdo);
|
||
#endif
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::_PnpDeviceUsageNotification(
|
||
__inout FxPkgPnp* This,
|
||
__inout FxIrp *Irp
|
||
)
|
||
{
|
||
return This->PnpDeviceUsageNotification(Irp);
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::PnpDeviceUsageNotification(
|
||
__inout FxIrp* Irp
|
||
)
|
||
{
|
||
FxRelatedDeviceList* pList;
|
||
FxRelatedDevice *pDependent;
|
||
FxAutoIrp relatedIrp(NULL), parentIrp(NULL);
|
||
MxDeviceObject topOfParentStack;
|
||
DEVICE_USAGE_NOTIFICATION_TYPE type;
|
||
NTSTATUS status;
|
||
MxDeviceObject pAttached;
|
||
MdIrp pNewIrp;
|
||
CCHAR maxStack;
|
||
BOOLEAN inPath, supported;
|
||
ULONG oldFlags;
|
||
MxAutoWorkItem workItem;
|
||
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"Entering DeviceUsageNotification handler");
|
||
|
||
status = STATUS_SUCCESS;
|
||
|
||
type = Irp->GetParameterUsageNotificationType();
|
||
inPath = Irp->GetParameterUsageNotificationInPath();
|
||
supported = FALSE;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"type %x, in path %x, can support paging %x, dump file %x, "
|
||
"hiber file %x, boot file %x",
|
||
type, inPath,
|
||
IsUsageSupported(_SpecialTypeToUsage(WdfSpecialFilePaging)),
|
||
IsUsageSupported(_SpecialTypeToUsage(WdfSpecialFileDump)),
|
||
IsUsageSupported(_SpecialTypeToUsage(WdfSpecialFileHibernation)),
|
||
IsUsageSupported(_SpecialTypeToUsage(WdfSpecialFileBoot)));
|
||
|
||
|
||
if (type >= WdfSpecialFilePaging && type < WdfSpecialFileMax) {
|
||
if (inPath) {
|
||
if (m_Device->IsFilter()) {
|
||
//
|
||
// Filters always support usage notifications
|
||
//
|
||
supported = TRUE;
|
||
}
|
||
else {
|
||
supported = IsUsageSupported(type);
|
||
}
|
||
}
|
||
else {
|
||
//
|
||
// We always handle notifications where we are out of the path
|
||
//
|
||
supported = TRUE;
|
||
}
|
||
}
|
||
|
||
if (supported == FALSE) {
|
||
status = STATUS_NOT_IMPLEMENTED;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"Usage type %x not supported, %!STATUS!", type, status);
|
||
|
||
return CompletePnpRequest(Irp, status);
|
||
}
|
||
|
||
//
|
||
// Usage notification IRP gets forwarded to parent stack or to
|
||
// dependent stack. Since in such cases (with deep device tree) the stack
|
||
// may run out quickly, ensure there is enough stack, otherwise use a
|
||
// workitem.
|
||
//
|
||
if (Mx::MxHasEnoughRemainingThreadStack() == FALSE &&
|
||
(m_Device->IsPdo() ||
|
||
m_UsageDependentDeviceList != NULL)) {
|
||
|
||
status = workItem.Allocate(m_Device->GetDeviceObject());
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFDEVICE %p !devobj %p could not allocate workitem "
|
||
"to send usage notification type %d, inpath %d, %!STATUS!",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject(),
|
||
type, inPath, status);
|
||
|
||
return CompletePnpRequest(Irp, status);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Usage notification is supported. Set the flags on the device object
|
||
// before processing this any further and save the current flags on the
|
||
// device object.
|
||
//
|
||
oldFlags = SetUsageNotificationFlags(type, inPath);
|
||
|
||
if (m_Device->IsPdo()) {
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
topOfParentStack.SetObject(
|
||
m_Device->m_ParentDevice->GetAttachedDeviceReference());
|
||
|
||
pNewIrp = FxIrp::AllocateIrp(topOfParentStack.GetStackSize());
|
||
if (pNewIrp != NULL) {
|
||
parentIrp.SetIrp(pNewIrp);
|
||
|
||
//
|
||
// parentIrp now owns the irp
|
||
//
|
||
pNewIrp = NULL;
|
||
|
||
status = SendDeviceUsageNotification(&topOfParentStack,
|
||
&parentIrp,
|
||
&workItem,
|
||
Irp,
|
||
FALSE);
|
||
}
|
||
else {
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFDEVICE %p could not allocate PIRP for parent !devobj %p to "
|
||
"send usage notification type %d, inpath %d, %!STATUS!",
|
||
m_Device->GetHandle(), topOfParentStack.GetObject(),
|
||
type, inPath, status);
|
||
}
|
||
topOfParentStack.DereferenceObject();
|
||
topOfParentStack.SetObject(NULL);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"Exit %!STATUS!", status);
|
||
|
||
RevertUsageNotificationFlags(type, inPath, oldFlags);
|
||
return CompletePnpRequest(Irp, status);
|
||
}
|
||
}
|
||
|
||
maxStack = 0;
|
||
pDependent = NULL;
|
||
|
||
//
|
||
// If the driver supports the given special file, lets notify dependent
|
||
// stacks.
|
||
//
|
||
|
||
//
|
||
// LockForEnum will lock out new changes to the list until we unlock the list.
|
||
// We remain at passive level once we are locked.
|
||
//
|
||
if (m_UsageDependentDeviceList != NULL) {
|
||
//
|
||
// We capture the m_UsageDependentDeviceList pointer value so that we
|
||
// always use the same pointer value and that we have matched actions
|
||
// (lock for enum / unlock from enum). What we are trying to avoid is
|
||
// this:
|
||
// 1) we do not lock for enum because m_UsageDependentDeviceList == NULL
|
||
// 2) in the middle of this function, m_UsageDependentDeviceList is
|
||
// assigned a pointer value
|
||
// 3) we try to unlock from enum later (or iterate, thinking the enum
|
||
// lock is held) by checking m_UsageDependentDeviceList for NULL, and
|
||
// now that is != NULL, use it.
|
||
//
|
||
// By capturing the pointer now, we either have a list or not and we don't
|
||
// hit situations 2 or 3. So, the rule is every subseqeunt time we need
|
||
// to check if there is valid m_UsageDependentDeviceList pointer, we
|
||
// use pList, but when we use the list, we can use m_UsageDependentDeviceList
|
||
// directly.
|
||
//
|
||
pList = m_UsageDependentDeviceList;
|
||
|
||
m_UsageDependentDeviceList->LockForEnum(GetDriverGlobals());
|
||
|
||
while ((pDependent = m_UsageDependentDeviceList->GetNextEntry(pDependent)) != NULL) {
|
||
|
||
MxDeviceObject deviceObject(pDependent->GetDevice());
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
pAttached.SetObject(deviceObject.GetAttachedDeviceReference());
|
||
|
||
if (pAttached.GetStackSize() > maxStack) {
|
||
maxStack = pAttached.GetStackSize();
|
||
}
|
||
|
||
pAttached.DereferenceObject();
|
||
}
|
||
}
|
||
else {
|
||
pList = NULL;
|
||
}
|
||
|
||
if (maxStack > 0) {
|
||
//
|
||
// If we have a maxStack size, we have a list as well
|
||
//
|
||
ASSERT(m_UsageDependentDeviceList != NULL);
|
||
|
||
//
|
||
// Allocate one irp for all the stacks so that we don't have an
|
||
// allocation later. This way, once we have the irp, we can send the
|
||
// usage notification to all stacks reliably, as well as the reverting
|
||
// of the notification if we encounter failure.
|
||
//
|
||
pNewIrp = FxIrp::AllocateIrp(maxStack);
|
||
if (pNewIrp == NULL) {
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFDEVICE %p could not allocate IRP to send usage notifications"
|
||
" to related stacks, type %d, inpath %d, status %!STATUS!",
|
||
m_Device->GetHandle(), type, inPath, status);
|
||
}
|
||
else {
|
||
MxDeviceObject dependentDevice;
|
||
|
||
//
|
||
// relatedIrp will free the irp when it goes out of scope
|
||
//
|
||
relatedIrp.SetIrp(pNewIrp);
|
||
|
||
//
|
||
// Walk our collection of dependent device stacks, and notify
|
||
// each stack of the device notification. If any fail, notify
|
||
// the stacks who already were told of the reverted behavior.
|
||
//
|
||
pDependent = NULL;
|
||
while ((pDependent = m_UsageDependentDeviceList->GetNextEntry(pDependent)) != NULL) {
|
||
dependentDevice.SetObject(pDependent->GetDevice());
|
||
status = SendDeviceUsageNotification(&dependentDevice,
|
||
&relatedIrp,
|
||
&workItem,
|
||
Irp,
|
||
FALSE);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
FxRelatedDevice* pDependent2;
|
||
|
||
pDependent2 = NULL;
|
||
|
||
//
|
||
// A device failed the device usage notification request.
|
||
// Notify the stacks that didn't fail, so they can unwind
|
||
// the operation.
|
||
//
|
||
while ((pDependent2 = m_UsageDependentDeviceList->GetNextEntry(pDependent2)) != NULL &&
|
||
pDependent2 != pDependent) {
|
||
dependentDevice.SetObject(pDependent2->GetDevice());
|
||
|
||
//
|
||
// We're already in a failure path. We can't do anything
|
||
// about yet another failure. So we ignore the return
|
||
// value.
|
||
//
|
||
(void) SendDeviceUsageNotification(&dependentDevice,
|
||
&relatedIrp,
|
||
&workItem,
|
||
Irp,
|
||
TRUE);
|
||
}
|
||
|
||
//
|
||
// Now break out of our outter loop.
|
||
//
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// If we are successful to this point, then send the IRP down the
|
||
// stack.
|
||
//
|
||
if (NT_SUCCESS(status)) {
|
||
BOOLEAN referenceSucceeded, sendDown;
|
||
|
||
referenceSucceeded = FALSE;
|
||
sendDown = TRUE;
|
||
|
||
//
|
||
// Make sure the stack is in D0 before sending down the request. This
|
||
// will at least guarantee that all devices below this one are in D0
|
||
// when the make the transition from power pageable to non or vice versa.
|
||
//
|
||
if (IsPowerPolicyOwner()) {
|
||
status = PowerReference(TRUE);
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
referenceSucceeded = TRUE;
|
||
}
|
||
else {
|
||
Irp->SetStatus(status);
|
||
sendDown = FALSE;
|
||
}
|
||
}
|
||
|
||
if (sendDown) {
|
||
//
|
||
// If we supported the usage, set the status to success, otherwise we
|
||
// keep the status in the irp as it arrived to this device
|
||
//
|
||
if (supported) {
|
||
Irp->SetStatus(status);
|
||
}
|
||
status = SendIrpSynchronously(Irp);
|
||
}
|
||
|
||
//
|
||
// Transitioning from a thread which was power pagable to non power
|
||
// pagable. We now need a power thread for the stack, ask for it.
|
||
// Note that there is no need for power thread in case of "boot"
|
||
// notification since boot notification doesn't require clearing device's
|
||
// DO_POWER_PAGABLE flag (power thread is required when handling power
|
||
// irp at dispatch level which can happen if the DO_POWER_PAGABLE flag
|
||
// is cleared).
|
||
//
|
||
// NOTE: Once we have a power thread, we never go back to using work
|
||
// items even though the stack may revert to power pagable.
|
||
// This is an acceptable tradeoff between resource usage and
|
||
// WDF complexity.
|
||
//
|
||
//
|
||
if (NT_SUCCESS(status) &&
|
||
inPath &&
|
||
(HasPowerThread() == FALSE) &&
|
||
type != WdfSpecialFileBoot
|
||
) {
|
||
status = QueryForPowerThread();
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
//
|
||
// Keep status the same through out so we can set it back in
|
||
// the irp when we are done.
|
||
//
|
||
if (m_Device->IsPdo()) {
|
||
//
|
||
// need to revert our parent's stack
|
||
//
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
topOfParentStack.SetObject(
|
||
m_Device->m_ParentDevice->GetAttachedDeviceReference());
|
||
|
||
//
|
||
// Ignore the status because we can't do anything on failure
|
||
//
|
||
(void) SendDeviceUsageNotification(&topOfParentStack,
|
||
&parentIrp,
|
||
&workItem,
|
||
Irp,
|
||
TRUE);
|
||
|
||
topOfParentStack.DereferenceObject();
|
||
}
|
||
else {
|
||
//
|
||
// Notify the stack below us
|
||
//
|
||
Irp->CopyCurrentIrpStackLocationToNext();
|
||
Irp->SetParameterUsageNotificationInPath(FALSE);
|
||
|
||
//
|
||
// Required for pnp irps
|
||
//
|
||
Irp->SetStatus(STATUS_NOT_SUPPORTED);
|
||
|
||
//
|
||
// Ignore the status because we can't do anything on failure
|
||
//
|
||
(void) Irp->SendIrpSynchronously(m_Device->GetAttachedDevice());
|
||
}
|
||
|
||
Irp->SetStatus(status);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Now check whether the lower devices succeeded or failed. If they
|
||
// failed, back out our changes and propogate the failure.
|
||
//
|
||
if (!NT_SUCCESS(status)) {
|
||
//
|
||
// Revert the flags set on the device object.
|
||
//
|
||
RevertUsageNotificationFlags(type, inPath, oldFlags);
|
||
|
||
//
|
||
// Notify dependent stacks of the failure.
|
||
//
|
||
pDependent = NULL;
|
||
|
||
//
|
||
// See pList initiatilazation as to why we compare pList for != NULL
|
||
// and not m_UsageDependentDeviceList.
|
||
//
|
||
if (pList != NULL) {
|
||
MxDeviceObject dependentDevice;
|
||
|
||
while ((pDependent = m_UsageDependentDeviceList->GetNextEntry(pDependent)) != NULL) {
|
||
dependentDevice.SetObject(pDependent->GetDevice());
|
||
|
||
//
|
||
// We're already in a failure path. We can't do anything
|
||
// about yet another failure. So we ignore the return value.
|
||
//
|
||
(void) SendDeviceUsageNotification(&dependentDevice,
|
||
&relatedIrp,
|
||
&workItem,
|
||
Irp,
|
||
TRUE);
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// By this point, we have propagated the notification to dependent devices
|
||
// and lower stack, and if anyone failed during that time, we also
|
||
// propagated failure to dependent stacks and lower stack.
|
||
// If status is success at this point, invoke the driver's callback.
|
||
//
|
||
if (NT_SUCCESS(status)) {
|
||
//
|
||
// Invoke callback. Note that only one of the callbacks
|
||
// DeviceUsageNotification or DeviceUsgeNotificationEx will get
|
||
// invoked since only one of the callbacks at a time is supported.
|
||
// We ensured that during registration of the callback.
|
||
// Note that Ex callback will return success if driver did not
|
||
// supply any callback.
|
||
//
|
||
m_DeviceUsageNotification.Invoke(m_Device->GetHandle(),
|
||
_UsageToSpecialType(type),
|
||
inPath);
|
||
|
||
status = m_DeviceUsageNotificationEx.Invoke(
|
||
m_Device->GetHandle(),
|
||
_UsageToSpecialType(type),
|
||
inPath
|
||
);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
//
|
||
// Driver's callback returned failure. We need to propagate
|
||
// failure to lower stack and dependent stacks.
|
||
//
|
||
//
|
||
// Keep status the same through out so we can set it back in
|
||
// the irp when we are done.
|
||
//
|
||
if (m_Device->IsPdo()) {
|
||
//
|
||
// need to revert our parent's stack
|
||
//
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
topOfParentStack.SetObject(
|
||
m_Device->m_ParentDevice->GetAttachedDeviceReference());
|
||
|
||
//
|
||
// Ignore the status because we can't do anything on failure
|
||
//
|
||
(void) SendDeviceUsageNotification(&topOfParentStack,
|
||
&parentIrp,
|
||
&workItem,
|
||
Irp,
|
||
TRUE);
|
||
|
||
topOfParentStack.DereferenceObject();
|
||
}
|
||
else {
|
||
//
|
||
// Notify the stack below us
|
||
//
|
||
Irp->CopyCurrentIrpStackLocationToNext();
|
||
Irp->SetParameterUsageNotificationInPath(FALSE);
|
||
|
||
//
|
||
// Required for pnp irps
|
||
//
|
||
Irp->SetStatus(STATUS_NOT_SUPPORTED);
|
||
|
||
//
|
||
// Ignore the status because we can't do anything on failure
|
||
//
|
||
(void) Irp->SendIrpSynchronously(m_Device->GetAttachedDevice());
|
||
}
|
||
|
||
Irp->SetStatus(status);
|
||
|
||
//
|
||
// Revert the flags set on the device object.
|
||
//
|
||
RevertUsageNotificationFlags(type, inPath, oldFlags);
|
||
|
||
//
|
||
// Notify dependent stacks of the failure.
|
||
//
|
||
pDependent = NULL;
|
||
|
||
//
|
||
// See pList initiatilazation as to why we compare pList for != NULL
|
||
// and not m_UsageDependentDeviceList.
|
||
//
|
||
if (pList != NULL) {
|
||
MxDeviceObject dependentDevice;
|
||
|
||
while ((pDependent = m_UsageDependentDeviceList->GetNextEntry(pDependent)) != NULL) {
|
||
dependentDevice.SetObject(pDependent->GetDevice());
|
||
|
||
//
|
||
// We're already in a failure path. We can't do anything
|
||
// about yet another failure. So we ignore the return value.
|
||
//
|
||
(void) SendDeviceUsageNotification(&dependentDevice,
|
||
&relatedIrp,
|
||
&workItem,
|
||
Irp,
|
||
TRUE);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
|
||
CommitUsageNotification(type, oldFlags);
|
||
|
||
//
|
||
// If we are in the dump file path, we cannot idle out because we
|
||
// can experience a crash dump at any time.
|
||
//
|
||
if (IsPowerPolicyOwner() && type == DeviceUsageTypeDumpFile) {
|
||
//
|
||
// Add a reference everytime we are notified of being in the
|
||
// path, no need to match the first inPath notification and the
|
||
// last !inPath notification.
|
||
//
|
||
if (inPath) {
|
||
NTSTATUS refStatus;
|
||
|
||
ASSERT(GetUsageCount(type) > 0);
|
||
|
||
//
|
||
// Since our previous synchronous power reference succeeded,
|
||
// an addtional reference while we are powered up should
|
||
// never fail.
|
||
//
|
||
refStatus = PowerReference(FALSE);
|
||
#if DBG
|
||
ASSERT(NT_SUCCESS(refStatus));
|
||
#else
|
||
UNREFERENCED_PARAMETER(refStatus);
|
||
#endif
|
||
}
|
||
else {
|
||
ASSERT(GetUsageCount(type) >= 0);
|
||
PowerDereference();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// We no longer need to be in D0 if we don't have to be.
|
||
//
|
||
if (referenceSucceeded) {
|
||
ASSERT(IsPowerPolicyOwner());
|
||
PowerDereference();
|
||
}
|
||
}
|
||
|
||
//
|
||
// See pList initiatilazation as to why we compare pList for != NULL
|
||
// and not m_UsageDependentDeviceList.
|
||
//
|
||
if (pList != NULL) {
|
||
m_UsageDependentDeviceList->UnlockFromEnum(GetDriverGlobals());
|
||
}
|
||
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"Exit %!STATUS!", status);
|
||
|
||
return CompletePnpRequest(Irp, status);
|
||
}
|
||
|
||
ULONG
|
||
FxPkgPnp::SetUsageNotificationFlags(
|
||
__in DEVICE_USAGE_NOTIFICATION_TYPE Type,
|
||
__in BOOLEAN InPath
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine sets the usage notification flags on the device object (for
|
||
non-boot usages) and updates the special file usage count .
|
||
|
||
Arguments:
|
||
|
||
Type - the special file type - paging, hibernate, dump or boot file.
|
||
|
||
InPath - indicates whether the system is creating or removing the special
|
||
file on the device.
|
||
|
||
Return Value:
|
||
|
||
Returns the old flags on the device object.
|
||
|
||
--*/
|
||
{
|
||
PFX_DRIVER_GLOBALS FxDriverGlobals = GetDriverGlobals();
|
||
ULONG oldFlags, newFlags;
|
||
|
||
oldFlags = m_Device->GetDeviceObjectFlags();
|
||
|
||
//
|
||
|
||
//
|
||
DoTraceLevelMessage(
|
||
FxDriverGlobals, TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"Before: type %d, in path %d, special count %d, flags 0x%x, "
|
||
"device %p (WDFDEVICE %p), is pageable capable %d",
|
||
Type, InPath, GetUsageCount(Type), oldFlags,
|
||
m_Device->GetDeviceObject(), m_Device->GetHandle(),
|
||
m_Device->IsPowerPageableCapable());
|
||
|
||
//
|
||
// Adjust our "special file count".
|
||
//
|
||
AdjustUsageCount(Type, InPath);
|
||
|
||
//
|
||
// Boot notification doesn't require updating device flags.
|
||
//
|
||
if (Type == WdfSpecialFileBoot) {
|
||
return oldFlags;
|
||
}
|
||
|
||
if (m_Device->IsFilter()) {
|
||
//
|
||
// Clear the previous flags and reset them to the attached
|
||
// device's flags
|
||
//
|
||
newFlags = oldFlags & ~(DO_POWER_PAGABLE | DO_POWER_INRUSH);
|
||
newFlags |= m_Device->GetAttachedDeviceObjectFlags() &
|
||
(DO_POWER_PAGABLE | DO_POWER_INRUSH);
|
||
m_Device->SetDeviceObjectFlags(newFlags);
|
||
}
|
||
else {
|
||
if (InPath) {
|
||
m_Device->SetDeviceObjectFlags(
|
||
m_Device->GetDeviceObjectFlags() & ~DO_POWER_PAGABLE
|
||
);
|
||
}
|
||
else {
|
||
if (m_Device->IsPowerPageableCapable() && IsInSpecialUse() == FALSE) {
|
||
m_Device->SetDeviceObjectFlags(
|
||
m_Device->GetDeviceObjectFlags() | DO_POWER_PAGABLE
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
return oldFlags;
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::RevertUsageNotificationFlags(
|
||
__in DEVICE_USAGE_NOTIFICATION_TYPE Type,
|
||
__in BOOLEAN InPath,
|
||
__in ULONG OldFlags
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine reverts the usage notification flags to the old flags on
|
||
the device object and updates the special file usage count.
|
||
|
||
Arguments:
|
||
|
||
Type - the special file type - paging, hibernate or dump file.
|
||
|
||
InPath - indicates whether the system is creating or removing the special
|
||
file on the device.
|
||
|
||
OldFlags - the previous flags on the device object.
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// Re-adjust our "special file count".
|
||
//
|
||
InPath = !InPath;
|
||
AdjustUsageCount(Type, InPath);
|
||
|
||
//
|
||
// Restore the flags on the device object.
|
||
//
|
||
m_Device->SetDeviceObjectFlags(OldFlags);
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::CommitUsageNotification(
|
||
__in DEVICE_USAGE_NOTIFICATION_TYPE Type,
|
||
__in ULONG OldFlags
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine commits the usage notification flags on the device object
|
||
and invokes the usage notification callbacks. If the current flags on
|
||
the device object indicates that there was a transition from power-pagable
|
||
to non power-pagable, or vice-versa, then an event is posted to the power
|
||
state machine to notify it of the change. After this routine is called
|
||
the PNP manager will no longer be able to disable the device.
|
||
|
||
Arguments:
|
||
|
||
Type - the special file type - paging, hibernation or crash dump file.
|
||
|
||
InPath - indicates whether the system is creating or removing the special
|
||
file on the device.
|
||
|
||
OldFlags - the previous flags on the device object.
|
||
|
||
--*/
|
||
{
|
||
PFX_DRIVER_GLOBALS FxDriverGlobals = GetDriverGlobals();
|
||
ULONG newFlags;
|
||
|
||
newFlags = m_Device->GetDeviceObjectFlags();
|
||
|
||
if ((OldFlags & DO_POWER_PAGABLE) == DO_POWER_PAGABLE &&
|
||
(newFlags & DO_POWER_PAGABLE) == 0) {
|
||
//
|
||
// We transitioned from a power pageable to a non power pageable
|
||
// device. Move the power state machine to the appropriate
|
||
// state.
|
||
//
|
||
PowerProcessEvent(PowerMarkNonpageable);
|
||
}
|
||
|
||
if ((OldFlags & DO_POWER_PAGABLE) == 0 &&
|
||
(newFlags & DO_POWER_PAGABLE) == DO_POWER_PAGABLE) {
|
||
//
|
||
// We transitioned from a non power pageable to a power pageable
|
||
// device. Move the power state machine to the appropriate
|
||
// state.
|
||
//
|
||
PowerProcessEvent(PowerMarkPageable);
|
||
}
|
||
|
||
//
|
||
// Notify PNP that it should no longer be able
|
||
// to disable this device.
|
||
//
|
||
MxDeviceObject physicalDeviceObject(
|
||
m_Device->GetPhysicalDevice()
|
||
);
|
||
physicalDeviceObject.InvalidateDeviceState(
|
||
m_Device->GetDeviceObject()
|
||
);
|
||
|
||
DoTraceLevelMessage(
|
||
FxDriverGlobals, TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"After: special count %d, flags 0x%x, device %p (WDFDEVICE %p)",
|
||
GetUsageCount(Type), newFlags,
|
||
m_Device->GetDeviceObject(), m_Device->GetHandle());
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::AddUsageDevice(
|
||
__in MdDeviceObject DependentDevice
|
||
)
|
||
{
|
||
FxRelatedDevice* pRelated;
|
||
NTSTATUS status;
|
||
|
||
if (m_UsageDependentDeviceList == NULL) {
|
||
KIRQL irql;
|
||
|
||
Lock(&irql);
|
||
if (m_UsageDependentDeviceList == NULL) {
|
||
m_UsageDependentDeviceList = new (GetDriverGlobals()) FxRelatedDeviceList();
|
||
|
||
if (m_UsageDependentDeviceList != NULL) {
|
||
status = STATUS_SUCCESS;
|
||
}
|
||
else {
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"Could not allocate usage device list for WDFDEVICE %p, "
|
||
"%!STATUS!", m_Device->GetHandle(), status);
|
||
}
|
||
|
||
}
|
||
else {
|
||
//
|
||
// another thread allocated the list already
|
||
//
|
||
status = STATUS_SUCCESS;
|
||
}
|
||
Unlock(irql);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
}
|
||
|
||
pRelated = new(GetDriverGlobals())
|
||
FxRelatedDevice(DependentDevice, GetDriverGlobals());
|
||
|
||
if (pRelated == NULL) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
status = m_UsageDependentDeviceList->Add(GetDriverGlobals(), pRelated);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
pRelated->DeleteFromFailedCreate();
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::RemoveUsageDevice(
|
||
__in MdDeviceObject DependentDevice
|
||
)
|
||
{
|
||
if (m_UsageDependentDeviceList != NULL) {
|
||
m_UsageDependentDeviceList->Remove(GetDriverGlobals(), DependentDevice);
|
||
}
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::AddRemovalDevice(
|
||
__in MdDeviceObject DependentDevice
|
||
)
|
||
{
|
||
FxRelatedDevice* pRelated;
|
||
NTSTATUS status;
|
||
|
||
if (m_RemovalDeviceList == NULL) {
|
||
KIRQL irql;
|
||
|
||
Lock(&irql);
|
||
if (m_RemovalDeviceList == NULL) {
|
||
m_RemovalDeviceList = new (GetDriverGlobals()) FxRelatedDeviceList();
|
||
|
||
if (m_RemovalDeviceList != NULL) {
|
||
status = STATUS_SUCCESS;
|
||
}
|
||
else {
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"Could not allocate removal device list for WDFDEVICE %p, "
|
||
"%!STATUS!", m_Device->GetHandle(), status);
|
||
}
|
||
}
|
||
else {
|
||
//
|
||
// another thread allocated the list already
|
||
//
|
||
status = STATUS_SUCCESS;
|
||
}
|
||
Unlock(irql);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
}
|
||
|
||
pRelated = new(GetDriverGlobals())
|
||
FxRelatedDevice(DependentDevice, GetDriverGlobals());
|
||
if (pRelated == NULL) {
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
status = m_RemovalDeviceList->Add(GetDriverGlobals(), pRelated);
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
//
|
||
// RemovalRelations are queried automatically by PnP when the device is
|
||
// going to be query removed. No need to tell pnp that the list changed
|
||
// until it needs to query for it.
|
||
//
|
||
DO_NOTHING();
|
||
}
|
||
else {
|
||
pRelated->DeleteFromFailedCreate();
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::RemoveRemovalDevice(
|
||
__in MdDeviceObject DependentDevice
|
||
)
|
||
{
|
||
if (m_RemovalDeviceList != NULL) {
|
||
m_RemovalDeviceList->Remove(GetDriverGlobals(), DependentDevice);
|
||
}
|
||
|
||
//
|
||
// RemovalRelations are queried automatically by PnP when the device is
|
||
// going to be query removed. No need to tell pnp that the list changed
|
||
// until it needs to query for it.
|
||
//
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::ClearRemovalDevicesList(
|
||
VOID
|
||
)
|
||
{
|
||
FxRelatedDevice* pEntry;
|
||
|
||
if (m_RemovalDeviceList == NULL) {
|
||
return;
|
||
}
|
||
|
||
m_RemovalDeviceList->LockForEnum(GetDriverGlobals());
|
||
while ((pEntry = m_RemovalDeviceList->GetNextEntry(NULL)) != NULL) {
|
||
m_RemovalDeviceList->Remove(GetDriverGlobals(), pEntry->GetDevice());
|
||
}
|
||
m_RemovalDeviceList->UnlockFromEnum(GetDriverGlobals());
|
||
|
||
//
|
||
// RemovalRelations are queried automatically by PnP when the device is
|
||
// going to be query removed. No need to tell pnp that the list changed
|
||
// until it needs to query for it.
|
||
//
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::SetInternalFailure(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Sets the failure field and then optionally invalidates the device state.
|
||
|
||
Arguments:
|
||
InvalidateState - If TRUE, the state is invalidated
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
m_InternalFailure = TRUE;
|
||
|
||
MxDeviceObject physicalDeviceObject(
|
||
m_Device->GetPhysicalDevice()
|
||
);
|
||
physicalDeviceObject.InvalidateDeviceState(
|
||
m_Device->GetDeviceObject()
|
||
);
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::SetPendingPnpIrp(
|
||
__inout FxIrp* Irp,
|
||
__in BOOLEAN MarkIrpPending
|
||
)
|
||
{
|
||
if (m_PendingPnPIrp != NULL ) {
|
||
FxIrp pendingIrp(m_PendingPnPIrp);
|
||
|
||
//
|
||
// A state changing pnp irp is already pended. If we don't bugcheck
|
||
// the pended pnp irp will be overwritten with new pnp irp and the old
|
||
// one may never get completed, which may have drastic implications (
|
||
// unresponsive system, power manager not sending Sx Irp etc.)
|
||
//
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"A new state changing pnp irp %!pnpmn! IRP %p arrived while another "
|
||
"pnp irp %!pnpmn! IRP %p is still pending WDFDEVICE %p\n",
|
||
Irp->GetMinorFunction(), Irp->GetIrp(),
|
||
pendingIrp.GetMinorFunction(),pendingIrp.GetIrp(),
|
||
m_Device->GetHandle());
|
||
|
||
FxVerifierBugCheck(GetDriverGlobals(), // globals
|
||
WDF_PNP_FATAL_ERROR, // specific type
|
||
(ULONG_PTR)m_Device->GetHandle(), //parm 2
|
||
(ULONG_PTR)Irp->GetIrp()); // parm 3
|
||
|
||
/* NOTREACHED */
|
||
return;
|
||
}
|
||
if (MarkIrpPending) {
|
||
Irp->MarkIrpPending();
|
||
}
|
||
m_PendingPnPIrp = Irp->GetIrp();
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::AllocateEnumInfo(
|
||
VOID
|
||
)
|
||
{
|
||
KIRQL irql;
|
||
NTSTATUS status;
|
||
|
||
if (m_EnumInfo != NULL) {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
Lock(&irql);
|
||
if (m_EnumInfo == NULL) {
|
||
m_EnumInfo = new (GetDriverGlobals()) FxEnumerationInfo(GetDriverGlobals());
|
||
|
||
if (m_EnumInfo != NULL) {
|
||
status = m_EnumInfo->Initialize();
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
delete m_EnumInfo;
|
||
m_EnumInfo = NULL;
|
||
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR,
|
||
TRACINGPNP,
|
||
"Could not initialize enum info for "
|
||
"WDFDEVICE %p, %!STATUS!",
|
||
m_Device->GetHandle(), status);
|
||
}
|
||
|
||
}
|
||
else {
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"Could not allocate enum info for WDFDEVICE %p, "
|
||
"%!STATUS!", m_Device->GetHandle(), status);
|
||
}
|
||
}
|
||
else {
|
||
//
|
||
// another thread allocated the list already
|
||
//
|
||
status = STATUS_SUCCESS;
|
||
}
|
||
Unlock(irql);
|
||
|
||
return status;
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::AddChildList(
|
||
__in FxChildList* List
|
||
)
|
||
{
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"Adding FxChildList %p, WDFCHILDLIST %p", List,
|
||
List->GetHandle());
|
||
|
||
m_EnumInfo->m_ChildListList.Add(GetDriverGlobals(),
|
||
&List->m_TransactionLink);
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::RemoveChildList(
|
||
__in FxChildList* List
|
||
)
|
||
{
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"Removing FxChildList %p, WDFCHILDLIST %p", List,
|
||
List->GetHandle());
|
||
|
||
m_EnumInfo->m_ChildListList.Remove(GetDriverGlobals(), &List->m_TransactionLink);
|
||
|
||
//
|
||
|
||
//
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::ChildListNotifyRemove(
|
||
__inout PLONG PendingCount
|
||
)
|
||
{
|
||
FxTransactionedEntry* ple;
|
||
|
||
ple = NULL;
|
||
|
||
if (m_EnumInfo != NULL) {
|
||
m_EnumInfo->m_ChildListList.LockForEnum(GetDriverGlobals());
|
||
while ((ple = m_EnumInfo->m_ChildListList.GetNextEntry(ple)) != NULL) {
|
||
FxChildList::_FromEntry(ple)->NotifyDeviceRemove(PendingCount);
|
||
}
|
||
m_EnumInfo->m_ChildListList.UnlockFromEnum(GetDriverGlobals());
|
||
}
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::AddQueryInterface(
|
||
__in FxQueryInterface* QI,
|
||
__in BOOLEAN Lock
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Add a query interface structure to the list of interfaces supported
|
||
|
||
Arguments:
|
||
QI - the interface to add
|
||
|
||
Lock - indication of the list lock should be acquired or not
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
SINGLE_LIST_ENTRY **ppPrev, *pCur;
|
||
|
||
if (Lock) {
|
||
m_QueryInterfaceLock.AcquireLock(GetDriverGlobals());
|
||
}
|
||
|
||
ASSERT(QI->m_Entry.Next == NULL);
|
||
|
||
//
|
||
// Iterate until we find the end of the list and then append the new
|
||
// structure. ppPrev is the pointer to the Next pointer value. When we
|
||
// get to the end, ppPrev will be equal to the Next field which points to NULL.
|
||
//
|
||
ppPrev = &m_QueryInterfaceHead.Next;
|
||
pCur = m_QueryInterfaceHead.Next;
|
||
|
||
while (pCur != NULL) {
|
||
ppPrev = &pCur->Next;
|
||
pCur = pCur->Next;
|
||
}
|
||
|
||
*ppPrev = &QI->m_Entry;
|
||
|
||
if (Lock) {
|
||
m_QueryInterfaceLock.ReleaseLock(GetDriverGlobals());
|
||
}
|
||
}
|
||
|
||
__drv_maxIRQL(DISPATCH_LEVEL)
|
||
__drv_minIRQL(DISPATCH_LEVEL)
|
||
__drv_requiresIRQL(DISPATCH_LEVEL)
|
||
__drv_sameIRQL
|
||
VOID
|
||
FxWatchdog::_WatchdogDpc(
|
||
__in PKDPC Dpc,
|
||
__in_opt PVOID Context,
|
||
__in_opt PVOID SystemArgument1,
|
||
__in_opt PVOID SystemArgument2
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine's job is to crash the machine, attempting to get some data
|
||
into the crashdump file (or minidump) about why the machine stopped
|
||
responding during an attempt to put the machine to sleep.
|
||
|
||
Arguments:
|
||
|
||
This - the instance of FxPkgPnp
|
||
|
||
Return Value:
|
||
|
||
this routine never returns
|
||
|
||
--*/
|
||
{
|
||
WDF_POWER_ROUTINE_TIMED_OUT_DATA data;
|
||
FxWatchdog* pThis;
|
||
CfxDevice* pDevice;
|
||
|
||
UNREFERENCED_PARAMETER(Dpc);
|
||
UNREFERENCED_PARAMETER(SystemArgument1);
|
||
UNREFERENCED_PARAMETER(SystemArgument2);
|
||
|
||
pThis = (FxWatchdog*) Context;
|
||
pDevice = pThis->m_PkgPnp->GetDevice();
|
||
|
||
DoTraceLevelMessage(pDevice->GetDriverGlobals(),
|
||
TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"The driver failed to return from a callback routine "
|
||
"in a reasonable period of time. This prevented the "
|
||
"machine from going to sleep or from hibernating. The "
|
||
"machine crashed because that was the best way to get "
|
||
"data about the cause of the crash into a minidump file.");
|
||
|
||
data.PowerState = pDevice->GetDevicePowerState();
|
||
data.PowerPolicyState = pDevice->GetDevicePowerPolicyState();
|
||
data.DeviceObject = reinterpret_cast<PDEVICE_OBJECT>(pDevice->GetDeviceObject());
|
||
data.Device = pDevice->GetHandle();
|
||
data.TimedOutThread = reinterpret_cast<PKTHREAD>(pThis->m_CallingThread);
|
||
|
||
FxVerifierBugCheck(pDevice->GetDriverGlobals(),
|
||
WDF_POWER_ROUTINE_TIMED_OUT,
|
||
(ULONG_PTR) &data);
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::CreatePowerThread(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Creates a power thread for the device node. This thread is share among all
|
||
the devices in the stack through the POWER_THREAD_INTERFACE structure and
|
||
PnP query interface.
|
||
|
||
Arguments:
|
||
None
|
||
|
||
Return Value:
|
||
NTSTATUS
|
||
|
||
--*/
|
||
{
|
||
FxSystemThread *pThread, *pOld;
|
||
NTSTATUS status;
|
||
|
||
status = FxSystemThread::_CreateAndInit(
|
||
&pThread,
|
||
GetDriverGlobals(),
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject());
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Simple locking logic in case N requests are conncurrent. (The requests
|
||
// never should be concurrent, but in the case that there are 2 usage
|
||
// notifications coming in from two different sources, it could
|
||
// theoritically happen.)
|
||
//
|
||
pOld = (FxSystemThread*) InterlockedCompareExchangePointer(
|
||
(PVOID*) &m_PowerThread, pThread, NULL);
|
||
|
||
if (pOld != NULL) {
|
||
//
|
||
// Someone also set the thread pointer value at the same time, free
|
||
// our new one here.
|
||
//
|
||
pThread->ExitThread();
|
||
pThread->DeleteObject();
|
||
}
|
||
|
||
m_HasPowerThread = TRUE;
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::ReleasePowerThread(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
If this device is the owner of the power thread, it kills the thread.
|
||
Otherwise, if this device has acquired the thread from a lower device,
|
||
release the reference now.
|
||
|
||
Arguments:
|
||
None
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
BOOLEAN hadThread;
|
||
|
||
hadThread = m_HasPowerThread;
|
||
|
||
//
|
||
// Set to FALSE before cleaning up the reference or thread itself in case
|
||
// there is some other context trying to enqueue. The only way that could
|
||
// be happening is if the power policy owner is not WDF and sends power irps
|
||
// after query remove or surprise remove.
|
||
//
|
||
m_HasPowerThread = FALSE;
|
||
|
||
//
|
||
// Check for ownership
|
||
//
|
||
if (m_PowerThread != NULL) {
|
||
|
||
FxCREvent event;
|
||
|
||
//
|
||
// Event on stack is used, which is fine since this code is invoked
|
||
// only in KM. Verify this assumption.
|
||
//
|
||
// If this code is ever needed for UM, m_PowerThreadEvent should be
|
||
// pre-initialized (simlar to the way m_RemoveEventUm is used)
|
||
//
|
||
WDF_VERIFY_KM_ONLY_CODE();
|
||
|
||
ASSERT(m_PowerThreadEvent == NULL);
|
||
m_PowerThreadEvent = event.GetSelfPointer();
|
||
|
||
if (InterlockedDecrement(&m_PowerThreadInterfaceReferenceCount) > 0) {
|
||
//
|
||
// Wait for all references to go away before exitting the thread.
|
||
// A reference will be taken for every device in the stack above this
|
||
// one which queried for the interface.
|
||
//
|
||
event.EnterCRAndWaitAndLeave();
|
||
}
|
||
|
||
m_PowerThreadEvent = NULL;
|
||
|
||
//
|
||
// Wait for the thread to exit and then delete it. Since we have
|
||
// turned off the power policy state machine, we can safely do this here.
|
||
// Any upper level clients will have also turned off their power policy
|
||
// state machines.
|
||
//
|
||
m_PowerThread->ExitThread();
|
||
m_PowerThread->DeleteObject();
|
||
|
||
m_PowerThread = NULL;
|
||
}
|
||
else if (hadThread) {
|
||
//
|
||
// Release our reference
|
||
//
|
||
m_PowerThreadInterface.Interface.InterfaceDereference(
|
||
m_PowerThreadInterface.Interface.Context
|
||
);
|
||
}
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::_PowerThreadInterfaceReference(
|
||
__inout PVOID Context
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Increments the ref count on the thread interface.
|
||
|
||
Arguments:
|
||
Context - FxPkgPnp*
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
LONG count;
|
||
|
||
count = InterlockedIncrement(
|
||
&((FxPkgPnp*) Context)->m_PowerThreadInterfaceReferenceCount
|
||
);
|
||
|
||
#if DBG
|
||
ASSERT(count >= 2);
|
||
#else
|
||
UNREFERENCED_PARAMETER(count);
|
||
#endif
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::_PowerThreadInterfaceDereference(
|
||
__inout PVOID Context
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Interface deref for the thread interface. If this is the last reference
|
||
released, an event is set so that the thread which waiting for the last ref
|
||
to go away can unblock.
|
||
|
||
Arguments:
|
||
Context - FxPkgPnp*
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
FxPkgPnp* pThis;
|
||
|
||
pThis = (FxPkgPnp*) Context;
|
||
|
||
if (InterlockedDecrement(&pThis->m_PowerThreadInterfaceReferenceCount) == 0) {
|
||
pThis->m_PowerThreadEvent->Set();
|
||
}
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::PnpPowerReferenceSelf(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Take a power reference during a query pnp transition.
|
||
|
||
Arguments:
|
||
None
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
if (IsPowerPolicyOwner()) {
|
||
//
|
||
// We want to synchronously wait to move into D0
|
||
//
|
||
return PowerReference(TRUE);
|
||
}
|
||
else {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::PnpPowerReferenceDuringQueryPnp(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Take a power reference during a query pnp transition.
|
||
|
||
Arguments:
|
||
None
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
if (IsPowerPolicyOwner()) {
|
||
//
|
||
// We want to synchronously wait to move into D0
|
||
//
|
||
return m_PowerPolicyMachine.m_Owner->
|
||
m_PowerIdleMachine.PowerReferenceWithFlags(
|
||
FxPowerReferenceSendPnpPowerUpEvent
|
||
);
|
||
}
|
||
else {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::PnpPowerDereferenceSelf(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Release the power reference taken during a query pnp transition
|
||
|
||
Arguments:
|
||
None
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
if (IsPowerPolicyOwner()) {
|
||
PowerDereference();
|
||
}
|
||
}
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::CompletePowerRequest(
|
||
__inout FxIrp* Irp,
|
||
__in NTSTATUS Status
|
||
)
|
||
{
|
||
MdIrp irp;
|
||
|
||
//
|
||
// Once we call CompleteRequest, 2 things happen
|
||
// 1) this object may go away
|
||
// 2) Irp->m_Irp will be cleared
|
||
//
|
||
// As such, we capture the underlying WDM objects so that we can use them
|
||
// to release the remove lock and use the PIRP *value* as a tag to release
|
||
// the remlock.
|
||
//
|
||
irp = Irp->GetIrp();
|
||
|
||
Irp->SetStatus(Status);
|
||
Irp->StartNextPowerIrp();
|
||
Irp->CompleteRequest(IO_NO_INCREMENT);
|
||
|
||
Mx::MxReleaseRemoveLock(m_Device->GetRemoveLock(),
|
||
irp);
|
||
|
||
return Status;
|
||
}
|
||
|
||
LONG
|
||
FxPkgPnp::GetPnpStateInternal(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Returns the pnp device state encoded into a ULONG. This state is the state
|
||
that is reported to PNp via IRP_MN_QUERY_PNP_DEVICE_STATE after it has been
|
||
decoded into the bits pnp expects
|
||
|
||
Arguments:
|
||
None
|
||
|
||
Return Value:
|
||
the current state bits
|
||
|
||
--*/
|
||
{
|
||
LONG state;
|
||
KIRQL irql;
|
||
|
||
//
|
||
// State is shared with the caps bits. Use a lock to guard against
|
||
// corruption of the value between these 2 values
|
||
//
|
||
Lock(&irql);
|
||
state = m_PnpStateAndCaps.Value & FxPnpStateMask;
|
||
Unlock(irql);
|
||
|
||
return state;
|
||
}
|
||
|
||
LONG
|
||
FxPkgPnp::GetPnpCapsInternal(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Returns the pnp device capabilities encoded into a LONG. This state is used
|
||
in reporting device capabilities via IRP_MN_QUERY_CAPABILITIES and filling
|
||
in the PDEVICE_CAPABILITIES structure.
|
||
|
||
Arguments:
|
||
None
|
||
|
||
Return Value:
|
||
the current pnp cap bits
|
||
|
||
--*/
|
||
{
|
||
LONG caps;
|
||
KIRQL irql;
|
||
|
||
Lock(&irql);
|
||
caps = m_PnpStateAndCaps.Value & FxPnpCapMask;
|
||
Unlock(irql);
|
||
|
||
return caps;
|
||
}
|
||
|
||
|
||
VOID
|
||
FxPkgPnp::SetPnpCaps(
|
||
__in PWDF_DEVICE_PNP_CAPABILITIES PnpCapabilities
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Encode the driver provided pnp capabilities into our internal capabilities
|
||
bit field and store the result.
|
||
|
||
Arguments:
|
||
PnpCapabilities - capabilities as reported by the driver writer
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
LONG pnpCaps;
|
||
KIRQL irql;
|
||
|
||
pnpCaps = 0;
|
||
pnpCaps |= GET_PNP_CAP_BITS_FROM_STRUCT(PnpCapabilities, LockSupported);
|
||
pnpCaps |= GET_PNP_CAP_BITS_FROM_STRUCT(PnpCapabilities, EjectSupported);
|
||
pnpCaps |= GET_PNP_CAP_BITS_FROM_STRUCT(PnpCapabilities, Removable);
|
||
pnpCaps |= GET_PNP_CAP_BITS_FROM_STRUCT(PnpCapabilities, DockDevice);
|
||
pnpCaps |= GET_PNP_CAP_BITS_FROM_STRUCT(PnpCapabilities, UniqueID);
|
||
pnpCaps |= GET_PNP_CAP_BITS_FROM_STRUCT(PnpCapabilities, SilentInstall);
|
||
pnpCaps |= GET_PNP_CAP_BITS_FROM_STRUCT(PnpCapabilities, SurpriseRemovalOK);
|
||
pnpCaps |= GET_PNP_CAP_BITS_FROM_STRUCT(PnpCapabilities, HardwareDisabled);
|
||
pnpCaps |= GET_PNP_CAP_BITS_FROM_STRUCT(PnpCapabilities, NoDisplayInUI);
|
||
|
||
//
|
||
// Since the caller of IRP_MN_QUERY_CAPABILITIES sets these 2 values to -1,
|
||
// we can reuse the -1 as the default/no override value since the associated
|
||
// PDEVICE_CAPAPBILITIES structure will have been predisposed to these values
|
||
//
|
||
if (PnpCapabilities->Address != (ULONG) -1) {
|
||
m_PnpCapsAddress = PnpCapabilities->Address;
|
||
}
|
||
if (PnpCapabilities->UINumber != (ULONG) -1) {
|
||
m_PnpCapsUINumber = PnpCapabilities->UINumber;
|
||
}
|
||
|
||
//
|
||
// Use the FxPnpStateMask to keep the state mask while applying the new
|
||
// pnp capabilities.
|
||
//
|
||
Lock(&irql);
|
||
m_PnpStateAndCaps.Value = (m_PnpStateAndCaps.Value & FxPnpStateMask) | pnpCaps;
|
||
Unlock(irql);
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::GetPnpState(
|
||
__out PWDF_DEVICE_STATE State
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Decodes our internal pnp state bitfield into the external WDF_DEVICE_STATE
|
||
structure
|
||
|
||
Arguments:
|
||
State - the structure to decode into
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
LONG state;
|
||
|
||
state = GetPnpStateInternal();
|
||
|
||
SET_TRI_STATE_FROM_STATE_BITS(state, State, Disabled);
|
||
SET_TRI_STATE_FROM_STATE_BITS(state, State, DontDisplayInUI);
|
||
SET_TRI_STATE_FROM_STATE_BITS(state, State, Failed);
|
||
SET_TRI_STATE_FROM_STATE_BITS(state, State, NotDisableable);
|
||
SET_TRI_STATE_FROM_STATE_BITS(state, State, Removed);
|
||
SET_TRI_STATE_FROM_STATE_BITS(state, State, ResourcesChanged);
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::SetPnpState(
|
||
__in PWDF_DEVICE_STATE State
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Encodes the driver writer provided state into our internal bit field.
|
||
|
||
Arguments:
|
||
State - the states to encode
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
LONG pnpState;
|
||
KIRQL irql;
|
||
|
||
pnpState = 0x0;
|
||
pnpState |= GET_PNP_STATE_BITS_FROM_STRUCT(State, Disabled);
|
||
pnpState |= GET_PNP_STATE_BITS_FROM_STRUCT(State, DontDisplayInUI);
|
||
pnpState |= GET_PNP_STATE_BITS_FROM_STRUCT(State, Failed);
|
||
pnpState |= GET_PNP_STATE_BITS_FROM_STRUCT(State, NotDisableable);
|
||
pnpState |= GET_PNP_STATE_BITS_FROM_STRUCT(State, Removed);
|
||
pnpState |= GET_PNP_STATE_BITS_FROM_STRUCT(State, ResourcesChanged);
|
||
|
||
//
|
||
// Mask off FxPnpCapMask to keep the capabilities part of the bitfield
|
||
// the same while change the pnp state.
|
||
//
|
||
Lock(&irql);
|
||
m_PnpStateAndCaps.Value = (m_PnpStateAndCaps.Value & FxPnpCapMask) | pnpState;
|
||
Unlock(irql);
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::_SetPowerCapState(
|
||
__in ULONG Index,
|
||
__in DEVICE_POWER_STATE State,
|
||
__out PULONG Result
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Encodes the given device power state (State) into Result at the given Index.
|
||
States are encoded in nibbles (4 bit chunks), starting at the bottom of the
|
||
result and moving upward
|
||
|
||
Arguments:
|
||
Index - zero based index into the number of nibbles to encode the value
|
||
|
||
State - State to encode
|
||
|
||
Result - pointer to where the encoding will take place
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// We store off state in 4 bits, starting at the lowest byte
|
||
//
|
||
ASSERT(Index < 8);
|
||
|
||
//
|
||
// Erase the old value
|
||
//
|
||
*Result &= ~(0xF << (Index * 4));
|
||
|
||
//
|
||
// Write in the new one
|
||
//
|
||
*Result |= (0xF & State) << (Index * 4);
|
||
}
|
||
|
||
DEVICE_POWER_STATE
|
||
FxPkgPnp::_GetPowerCapState(
|
||
__in ULONG Index,
|
||
__in ULONG State
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Decodes our internal device state encoding and returns a normalized device
|
||
power state for the given index.
|
||
|
||
Arguments:
|
||
Index - nibble (4 bit chunk) index into the State
|
||
|
||
State - value which has the device states encoded into it
|
||
|
||
Return Value:
|
||
device power state for the given Index
|
||
|
||
--*/
|
||
{
|
||
ASSERT(Index < 8);
|
||
// isolate the value and normalize it
|
||
return (DEVICE_POWER_STATE) ((State & (0xF << (Index * 4))) >> (Index * 4));
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::SetPowerCaps(
|
||
__in PWDF_DEVICE_POWER_CAPABILITIES PowerCapabilities
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Encodes the driver provided power capabilities into the object. The device
|
||
power states are encoded into one ULONG while the other power caps are
|
||
encoded into their own distinct fields.
|
||
|
||
Arguments:
|
||
PowerCapabilities - the power caps reported by the driver writer
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
ULONG states, i;
|
||
USHORT powerCaps;
|
||
|
||
states = 0x0;
|
||
|
||
//
|
||
// Build up the device power state encoding into a temp var so that if we are
|
||
// retrieving the encoding in another thread, we don't get a partial view
|
||
//
|
||
for (i = 0; i < ARRAY_SIZE(PowerCapabilities->DeviceState); i++) {
|
||
_SetPowerCapState(i, PowerCapabilities->DeviceState[i], &states);
|
||
}
|
||
|
||
m_PowerCaps.States = states;
|
||
|
||
//
|
||
// Same idea. Build up the caps locally first so that when we assign them
|
||
// into the object, it is assigned as a whole.
|
||
//
|
||
powerCaps = 0x0;
|
||
powerCaps |= GET_POWER_CAP_BITS_FROM_STRUCT(PowerCapabilities, DeviceD1);
|
||
powerCaps |= GET_POWER_CAP_BITS_FROM_STRUCT(PowerCapabilities, DeviceD2);
|
||
powerCaps |= GET_POWER_CAP_BITS_FROM_STRUCT(PowerCapabilities, WakeFromD0);
|
||
powerCaps |= GET_POWER_CAP_BITS_FROM_STRUCT(PowerCapabilities, WakeFromD1);
|
||
powerCaps |= GET_POWER_CAP_BITS_FROM_STRUCT(PowerCapabilities, WakeFromD2);
|
||
powerCaps |= GET_POWER_CAP_BITS_FROM_STRUCT(PowerCapabilities, WakeFromD3);
|
||
|
||
m_PowerCaps.Caps = powerCaps;
|
||
|
||
if (PowerCapabilities->DeviceWake != PowerDeviceMaximum) {
|
||
m_PowerCaps.DeviceWake = (BYTE) PowerCapabilities->DeviceWake;
|
||
}
|
||
if (PowerCapabilities->SystemWake != PowerSystemMaximum) {
|
||
m_PowerCaps.SystemWake = (BYTE) PowerCapabilities->SystemWake;
|
||
}
|
||
|
||
m_PowerCaps.D1Latency = PowerCapabilities->D1Latency;
|
||
m_PowerCaps.D2Latency = PowerCapabilities->D2Latency;
|
||
m_PowerCaps.D3Latency = PowerCapabilities->D3Latency;
|
||
|
||
if (PowerCapabilities->IdealDxStateForSx != PowerDeviceMaximum) {
|
||
//
|
||
// Caller has already validated that IdealDxStateForSx is only set if
|
||
// they device is the power policy owner.
|
||
//
|
||
m_PowerPolicyMachine.m_Owner->m_IdealDxStateForSx = (BYTE)
|
||
PowerCapabilities->IdealDxStateForSx;
|
||
}
|
||
}
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::CompletePnpRequest(
|
||
__inout FxIrp* Irp,
|
||
__in NTSTATUS Status
|
||
)
|
||
{
|
||
MdIrp pIrp = Irp->GetIrp();
|
||
|
||
Irp->SetStatus(Status);
|
||
Irp->CompleteRequest(IO_NO_INCREMENT);
|
||
|
||
Mx::MxReleaseRemoveLock(m_Device->GetRemoveLock(),
|
||
pIrp);
|
||
|
||
return Status;
|
||
}
|
||
|
||
BOOLEAN
|
||
FxPkgPnp::PowerPolicyIsWakeEnabled(
|
||
VOID
|
||
)
|
||
{
|
||
if (IsPowerPolicyOwner() && PowerPolicyGetCurrentWakeReason() != 0x0) {
|
||
return TRUE;
|
||
}
|
||
else {
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
ULONG
|
||
FxPkgPnp::PowerPolicyGetCurrentWakeReason(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
This routine determines the reasons for whether wake should be enabled or
|
||
not. Wake could be enabled because it is explicitly enabled for the device
|
||
in the wake policy settings or, because the device opted to depend on its
|
||
children being armed for wake.
|
||
|
||
Arguments:
|
||
None
|
||
|
||
Return Value:
|
||
Returns a combination of FxPowerPolicySxWakeChildrenArmedFlag, to indicate
|
||
that wake can be enabled because of more than one children being armed for
|
||
wake, and FxPowerPolicySxWakeDeviceEnabledFlag, to indicate that wake can
|
||
be enabled because the device was explicitly enabled in the wake policy
|
||
settings.
|
||
|
||
Returns Zero to indicate that wake is currently disabled for the device.
|
||
|
||
--*/
|
||
{
|
||
ULONG wakeReason;
|
||
|
||
wakeReason = 0x0;
|
||
|
||
if (m_PowerPolicyMachine.m_Owner->m_WakeSettings.ArmForWakeIfChildrenAreArmedForWake &&
|
||
m_PowerPolicyMachine.m_Owner->m_ChildrenArmedCount > 0) {
|
||
//
|
||
// Wake settings depends on children and one or more children are
|
||
// armed for wake.
|
||
//
|
||
wakeReason |= FxPowerPolicySxWakeChildrenArmedFlag;
|
||
}
|
||
|
||
if (m_PowerPolicyMachine.m_Owner->m_WakeSettings.Enabled) {
|
||
//
|
||
// Wake settings is explicitly enabled.
|
||
//
|
||
wakeReason |= FxPowerPolicySxWakeDeviceEnabledFlag;
|
||
}
|
||
|
||
return wakeReason;
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::SaveState(
|
||
__in BOOLEAN UseCanSaveState
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Saves any permanent state of the device out to the registry
|
||
|
||
Arguments:
|
||
None
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
UNICODE_STRING name;
|
||
FxAutoRegKey hKey;
|
||
NTSTATUS status;
|
||
ULONG value;
|
||
|
||
//
|
||
// We only have settings to save if we are the power policy owner
|
||
//
|
||
if (IsPowerPolicyOwner() == FALSE) {
|
||
return;
|
||
}
|
||
|
||
if (UseCanSaveState &&
|
||
m_PowerPolicyMachine.m_Owner->m_CanSaveState == FALSE) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGPNP,
|
||
"Not saving wake settings for WDFDEVICE %p due to system power "
|
||
"transition", m_Device->GetHandle());
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Check to see if there is anything to write out
|
||
//
|
||
if (m_PowerPolicyMachine.m_Owner->m_IdleSettings.Dirty == FALSE &&
|
||
m_PowerPolicyMachine.m_Owner->m_WakeSettings.Dirty == FALSE) {
|
||
return;
|
||
}
|
||
|
||
//
|
||
// No need to write out if user control is not enabled
|
||
//
|
||
if (m_PowerPolicyMachine.m_Owner->m_IdleSettings.Overridable == FALSE &&
|
||
m_PowerPolicyMachine.m_Owner->m_WakeSettings.Overridable == FALSE) {
|
||
return;
|
||
}
|
||
|
||
//
|
||
// If the device is in paging path we should not be touching registry during
|
||
// power up because it may incur page fault which won't be satisfied if the
|
||
// device is still not powered up, blocking power Irp. User control state
|
||
// change will not get written if any at this time but will be flushed out
|
||
// to registry during device disable/remove in the remove path.
|
||
//
|
||
if (IsUsageSupported(DeviceUsageTypePaging) && IsDevicePowerUpIrpPending()) {
|
||
return;
|
||
}
|
||
|
||
#if ((FX_CORE_MODE)==(FX_CORE_KERNEL_MODE))
|
||
|
||
status = m_Device->OpenSettingsKey(&hKey.m_Key, STANDARD_RIGHTS_WRITE);
|
||
if (!NT_SUCCESS(status)) {
|
||
return;
|
||
}
|
||
#else
|
||
status = STATUS_SUCCESS;
|
||
#endif
|
||
|
||
if (m_PowerPolicyMachine.m_Owner->m_IdleSettings.Overridable &&
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.Dirty) {
|
||
RtlInitUnicodeString(&name, WDF_S0_IDLE_ENABLED_VALUE_NAME);
|
||
value = m_PowerPolicyMachine.m_Owner->m_IdleSettings.Enabled;
|
||
|
||
WriteStateToRegistry(hKey.m_Key, &name, value);
|
||
|
||
m_PowerPolicyMachine.m_Owner->m_IdleSettings.Dirty = FALSE;
|
||
}
|
||
|
||
if (m_PowerPolicyMachine.m_Owner->m_WakeSettings.Overridable &&
|
||
m_PowerPolicyMachine.m_Owner->m_WakeSettings.Dirty) {
|
||
RtlInitUnicodeString(&name, WDF_SX_WAKE_ENABLED_VALUE_NAME);
|
||
value = m_PowerPolicyMachine.m_Owner->m_WakeSettings.Enabled;
|
||
|
||
WriteStateToRegistry(hKey.m_Key, &name, value);
|
||
|
||
m_PowerPolicyMachine.m_Owner->m_WakeSettings.Dirty = FALSE;
|
||
}
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::AddInterruptObject(
|
||
__in FxInterrupt* Interrupt
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine adds a WDFINTERRUPT object onto the list of interrupts
|
||
which are attached to this device. This list is used in response to
|
||
several IRPs.
|
||
|
||
Note:
|
||
|
||
It shouldn't be necessary to lock this list, since the driver will add or
|
||
remove interrupt objects only in callbacks that are effectively serialized
|
||
by the PnP manager.
|
||
|
||
Further note:
|
||
|
||
This list must remain sorted by order of interrupt object creation. E.g.
|
||
the first interrupt object created must be first in this list.
|
||
|
||
Arguments:
|
||
|
||
Interrupt - a Framework interrupt object
|
||
|
||
Return Value:
|
||
|
||
VOID
|
||
|
||
--*/
|
||
{
|
||
m_InterruptObjectCount++;
|
||
InsertTailList(&m_InterruptListHead, &Interrupt->m_PnpList);
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::RemoveInterruptObject(
|
||
__in FxInterrupt* Interrupt
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
This routine removes a WDFINTERRUPT object onto the list of interrupts
|
||
which are attached to this device. This list is used in response to
|
||
several IRPs.
|
||
|
||
Arguments:
|
||
|
||
Interrupt - a Framework interrupt object
|
||
|
||
Return Value:
|
||
|
||
VOID
|
||
|
||
--*/
|
||
{
|
||
m_InterruptObjectCount--;
|
||
RemoveEntryList(&Interrupt->m_PnpList);
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::NotifyResourceobjectsToReleaseResources(
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine traverses all resource objects and tells them that their
|
||
resources are no longer valid.
|
||
|
||
Arguments:
|
||
|
||
none
|
||
|
||
Return Value:
|
||
|
||
VOID
|
||
|
||
--*/
|
||
{
|
||
FxInterrupt* interruptInstance;
|
||
PLIST_ENTRY intListEntry;
|
||
|
||
//
|
||
// Revoke each of the interrupts.
|
||
//
|
||
|
||
intListEntry = m_InterruptListHead.Flink;
|
||
|
||
while (intListEntry != &m_InterruptListHead) {
|
||
|
||
//
|
||
// Disconnect interrupts and then tell them that they no longer
|
||
// own their resources.
|
||
//
|
||
|
||
interruptInstance = CONTAINING_RECORD(intListEntry, FxInterrupt, m_PnpList);
|
||
interruptInstance->RevokeResources();
|
||
intListEntry = intListEntry->Flink;
|
||
}
|
||
|
||
//
|
||
// Now revoke each of the DMA enablers (only system-mode enablers
|
||
// will be affected by this)
|
||
//
|
||
|
||
if (m_DmaEnablerList != NULL) {
|
||
FxTransactionedEntry* listEntry;
|
||
|
||
m_DmaEnablerList->LockForEnum(GetDriverGlobals());
|
||
|
||
for (listEntry = m_DmaEnablerList->GetNextEntry(NULL);
|
||
listEntry != NULL;
|
||
listEntry = m_DmaEnablerList->GetNextEntry(listEntry)) {
|
||
RevokeDmaEnablerResources(
|
||
(FxDmaEnabler *) listEntry->GetTransactionedObject()
|
||
);
|
||
}
|
||
|
||
m_DmaEnablerList->UnlockFromEnum(GetDriverGlobals());
|
||
}
|
||
}
|
||
|
||
_Must_inspect_result_
|
||
NTSTATUS
|
||
FxPkgPnp::NotifyResourceObjectsD0(
|
||
__in ULONG NotifyFlags
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine traverses all resource objects and tells them that the device
|
||
is entering D0. If an error is encountered, the walking of the list is
|
||
halted.
|
||
|
||
Arguments:
|
||
|
||
none
|
||
|
||
Return Value:
|
||
|
||
VOID
|
||
|
||
--*/
|
||
{
|
||
FxInterrupt* pInterrupt;
|
||
PLIST_ENTRY ple;
|
||
NTSTATUS status;
|
||
|
||
for (ple = m_InterruptListHead.Flink;
|
||
ple != &m_InterruptListHead;
|
||
ple = ple->Flink) {
|
||
|
||
//
|
||
// Connect the interrupts
|
||
//
|
||
pInterrupt = CONTAINING_RECORD(ple, FxInterrupt, m_PnpList);
|
||
|
||
status = pInterrupt->Connect(NotifyFlags);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFINTERRUPT %p failed to connect, %!STATUS!",
|
||
pInterrupt->GetHandle(), status);
|
||
|
||
return status;
|
||
}
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::NotifyResourceObjectsDx(
|
||
__in ULONG NotifyFlags
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
This routine traverses all resource objects and tells them that the device
|
||
is leaving D0. If there is an error, the remaining resources in the list
|
||
are still notified of exitting D0.
|
||
|
||
Arguments:
|
||
NotifyFlags - combination of values from the enum NotifyResourcesFlags
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
FxInterrupt* pInterrupt;
|
||
PLIST_ENTRY ple;
|
||
NTSTATUS status, finalStatus;
|
||
|
||
finalStatus = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Disconect in the reverse order in which we connected the interrupts
|
||
//
|
||
for (ple = m_InterruptListHead.Blink;
|
||
ple != &m_InterruptListHead;
|
||
ple = ple->Blink) {
|
||
|
||
//
|
||
// Disconnect interrupts and then tell them that they no longer
|
||
// own their resources.
|
||
//
|
||
pInterrupt = CONTAINING_RECORD(ple, FxInterrupt, m_PnpList);
|
||
|
||
status = pInterrupt->Disconnect(NotifyFlags);
|
||
|
||
if (!NT_SUCCESS(status)) {
|
||
//
|
||
// When we encounter an error we still disconnect the remaining
|
||
// interrupts b/c we would just do it later during device tear down
|
||
// (might as well do it now).
|
||
//
|
||
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFINTERRUPT %p failed to disconnect, %!STATUS!",
|
||
pInterrupt->GetHandle(), status);
|
||
//
|
||
// Overwriting a previous finalStatus is OK, we'll just report the
|
||
// last one
|
||
//
|
||
finalStatus = status;
|
||
}
|
||
}
|
||
|
||
return finalStatus;
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::SendEventToAllWakeInterrupts(
|
||
__in FxWakeInterruptEvents WakeInterruptEvent
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
This routine traverses all interrupt objects and queues the
|
||
given event into those interrupts that are marked as wake
|
||
capable and have a state machine
|
||
|
||
Arguments:
|
||
WakeInterruptEvent - Event to be queued in the wake interrupt
|
||
state machines
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
FxInterrupt* pInterrupt;
|
||
PLIST_ENTRY ple;
|
||
|
||
if (m_WakeInterruptCount == 0) {
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Initialize the pending count to the wake interrupt count
|
||
// If the event needs an acknowledgement, this variable will
|
||
// be decremented as the interrupt machines ack.
|
||
//
|
||
m_WakeInterruptPendingAckCount = m_WakeInterruptCount;
|
||
|
||
for (ple = m_InterruptListHead.Flink;
|
||
ple != &m_InterruptListHead;
|
||
ple = ple->Flink) {
|
||
|
||
pInterrupt = CONTAINING_RECORD(ple, FxInterrupt, m_PnpList);
|
||
|
||
if (pInterrupt->IsWakeCapable()) {
|
||
pInterrupt->ProcessWakeInterruptEvent(WakeInterruptEvent);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
VOID
|
||
FxPkgPnp::AckPendingWakeInterruptOperation(
|
||
__in BOOLEAN ProcessPowerEventOnDifferentThread
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
This routine is invoked by the interrupt wake machine to acknowledge
|
||
back an event that was queued into it. When the last interrupt machine
|
||
acknowledges, an event is queued back into the Pnp/power state machine
|
||
|
||
Arguments:
|
||
|
||
ProcessPowerEventOnDifferentThread - Once all wake interrupts for the device
|
||
have acknowledged the operation, if this is TRUE, the power state
|
||
machine will process the PowerWakeInterruptCompleteTransition event on a
|
||
seperate thread.
|
||
|
||
Return Value:
|
||
None
|
||
|
||
--*/
|
||
{
|
||
if (InterlockedDecrement((LONG *)&m_WakeInterruptPendingAckCount) == 0) {
|
||
PowerProcessEvent(
|
||
PowerWakeInterruptCompleteTransition,
|
||
ProcessPowerEventOnDifferentThread
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
FxPkgPnp::AssignPowerFrameworkSettings(
|
||
__in PWDF_POWER_FRAMEWORK_SETTINGS PowerFrameworkSettings
|
||
)
|
||
{
|
||
NTSTATUS status;
|
||
PPO_FX_COMPONENT_IDLE_STATE idleStates = NULL;
|
||
ULONG idleStatesSize = 0;
|
||
PPO_FX_COMPONENT component = NULL;
|
||
ULONG componentSize = 0;
|
||
PPOX_SETTINGS poxSettings = NULL;
|
||
ULONG poxSettingsSize = 0;
|
||
BYTE * buffer = NULL;
|
||
|
||
if (FALSE==(IdleTimeoutManagement::_SystemManagedIdleTimeoutAvailable())) {
|
||
//
|
||
// If system-managed idle timeout is not available on this OS, then
|
||
// there is nothing to do.
|
||
//
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGPNP,
|
||
"WDFDEVICE %p !devobj %p Power framework is not supported on the "
|
||
"current OS. Therefore, the power framework settings will not take "
|
||
"effect.",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject()
|
||
);
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
if (NULL != PowerFrameworkSettings->Component) {
|
||
//
|
||
// Caller should ensure that IdleStateCount is not zero
|
||
//
|
||
ASSERT(0 != PowerFrameworkSettings->Component->IdleStateCount);
|
||
|
||
//
|
||
// Compute buffer size needed for storing F-states
|
||
//
|
||
status = RtlULongMult(
|
||
PowerFrameworkSettings->Component->IdleStateCount,
|
||
sizeof(*(PowerFrameworkSettings->Component->IdleStates)),
|
||
&idleStatesSize
|
||
);
|
||
if (FALSE == NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFDEVICE %p !devobj %p Unable to compute length of buffer "
|
||
"required to store F-states. RtlULongMult failed with "
|
||
"%!STATUS!",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject(),
|
||
status
|
||
);
|
||
goto exit;
|
||
}
|
||
|
||
//
|
||
// Compute buffer size needed for storing component information
|
||
// (including F-states)
|
||
//
|
||
status = RtlULongAdd(idleStatesSize,
|
||
sizeof(*component),
|
||
&componentSize);
|
||
if (FALSE == NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFDEVICE %p !devobj %p Unable to compute length of buffer "
|
||
"required to store driver's component information. RtlULongAdd "
|
||
"failed with %!STATUS!",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject(),
|
||
status
|
||
);
|
||
goto exit;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Compute total buffer size needed for power framework settings
|
||
//
|
||
status = RtlULongAdd(componentSize,
|
||
sizeof(*poxSettings),
|
||
&poxSettingsSize);
|
||
if (FALSE == NT_SUCCESS(status)) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFDEVICE %p !devobj %p Unable to compute length of buffer "
|
||
"required to store driver's power framework settings. RtlULongAdd "
|
||
"failed with %!STATUS!",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject(),
|
||
status
|
||
);
|
||
goto exit;
|
||
}
|
||
|
||
//
|
||
// Allocate memory to copy the settings
|
||
//
|
||
buffer = (BYTE *) MxMemory::MxAllocatePoolWithTag(NonPagedPool,
|
||
poxSettingsSize,
|
||
GetDriverGlobals()->Tag);
|
||
if (NULL == buffer) {
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
|
||
"WDFDEVICE %p !devobj %p Unable to allocate buffer required to "
|
||
"store F-states. %!STATUS!",
|
||
m_Device->GetHandle(),
|
||
m_Device->GetDeviceObject(),
|
||
status
|
||
);
|
||
goto exit;
|
||
}
|
||
|
||
//
|
||
// Set our pointers to point to appropriate locations in the buffer.
|
||
//
|
||
// NOTES:
|
||
// - The array of F-states comes first because it has ULONGLONG members
|
||
// because of which it has the biggest alignment requirement.
|
||
// - The logic below works even if the client driver did not specify any
|
||
// component information. In that case idleStatesSize and componentSize
|
||
// are both 0 and 'poxSettings' points to the beginning of the allocated
|
||
// buffer
|
||
//
|
||
idleStates = (PPO_FX_COMPONENT_IDLE_STATE) buffer;
|
||
component = (PPO_FX_COMPONENT) (buffer + idleStatesSize);
|
||
poxSettings = (PPOX_SETTINGS) (buffer + componentSize);
|
||
|
||
//
|
||
// Copy the relevant parts of the settings buffer
|
||
//
|
||
poxSettings->EvtDeviceWdmPostPoFxRegisterDevice =
|
||
PowerFrameworkSettings->EvtDeviceWdmPostPoFxRegisterDevice;
|
||
poxSettings->EvtDeviceWdmPrePoFxUnregisterDevice =
|
||
PowerFrameworkSettings->EvtDeviceWdmPrePoFxUnregisterDevice;
|
||
poxSettings->Component = PowerFrameworkSettings->Component;
|
||
poxSettings->ComponentActiveConditionCallback =
|
||
PowerFrameworkSettings->ComponentActiveConditionCallback;
|
||
poxSettings->ComponentIdleConditionCallback =
|
||
PowerFrameworkSettings->ComponentIdleConditionCallback;
|
||
poxSettings->ComponentIdleStateCallback =
|
||
PowerFrameworkSettings->ComponentIdleStateCallback;
|
||
poxSettings->PowerControlCallback =
|
||
PowerFrameworkSettings->PowerControlCallback;
|
||
poxSettings->PoFxDeviceContext = PowerFrameworkSettings->PoFxDeviceContext;
|
||
|
||
if (NULL != PowerFrameworkSettings->Component) {
|
||
//
|
||
// Copy the component information
|
||
//
|
||
poxSettings->Component = component;
|
||
RtlCopyMemory(poxSettings->Component,
|
||
PowerFrameworkSettings->Component,
|
||
sizeof(*component));
|
||
|
||
//
|
||
// Caller should ensure that IdleStates is not NULL
|
||
//
|
||
ASSERT(NULL != PowerFrameworkSettings->Component->IdleStates);
|
||
|
||
//
|
||
// Copy the F-states
|
||
//
|
||
poxSettings->Component->IdleStates = idleStates;
|
||
RtlCopyMemory(poxSettings->Component->IdleStates,
|
||
PowerFrameworkSettings->Component->IdleStates,
|
||
idleStatesSize);
|
||
}
|
||
|
||
//
|
||
// Commit these settings
|
||
//
|
||
status = m_PowerPolicyMachine.m_Owner->
|
||
m_IdleSettings.m_TimeoutMgmt.CommitPowerFrameworkSettings(
|
||
GetDriverGlobals(),
|
||
poxSettings
|
||
);
|
||
if (FALSE == NT_SUCCESS(status)) {
|
||
goto exit;
|
||
}
|
||
|
||
status = STATUS_SUCCESS;
|
||
|
||
exit:
|
||
if (FALSE == NT_SUCCESS(status)) {
|
||
if (NULL != buffer) {
|
||
MxMemory::MxFreePool(buffer);
|
||
}
|
||
}
|
||
return status;
|
||
}
|
||
|
||
DEVICE_POWER_STATE
|
||
FxPkgPnp::PowerPolicyGetDeviceDeepestDeviceWakeState(
|
||
__in SYSTEM_POWER_STATE SystemState
|
||
)
|
||
{
|
||
DEVICE_POWER_STATE dxState;
|
||
|
||
//
|
||
// Earlier versions of WDF (pre-1.11) did not take into account SystemState
|
||
// in figuring out the deepest wake state. So for compatibility we restrict
|
||
// this to drivers compiled for 1.11 or newer. See comments in the
|
||
// FxPkgPnp::QueryForCapabilities function for more information on
|
||
// compatibility risk.
|
||
//
|
||
if (GetDriverGlobals()->IsVersionGreaterThanOrEqualTo(1,11)) {
|
||
if ((SystemState < PowerSystemWorking) ||
|
||
(SystemState > PowerSystemHibernate)) {
|
||
dxState = PowerDeviceD0;
|
||
} else {
|
||
dxState = MapWakeDepthToDstate(
|
||
(DEVICE_WAKE_DEPTH)m_DeviceWake[SystemState - PowerSystemWorking]
|
||
);
|
||
}
|
||
}
|
||
else {
|
||
//
|
||
// For pre-1.11 drivers, all of m_DeviceWake array is populated with
|
||
// the same DeviceWake value obtained from device capabilities so we
|
||
// just use the first one (index 0).
|
||
//
|
||
dxState = MapWakeDepthToDstate((DEVICE_WAKE_DEPTH)m_DeviceWake[0]);
|
||
}
|
||
|
||
//
|
||
// Per WDM docs DeviceWake and SystemWake must be non-zero to support
|
||
// wake. We warn about the violation. Ideally, a verifier check would have
|
||
// been better but we want to avoid that because some drivers may
|
||
// intentionally call this DDI and do not treat the DDI failure as fatal (
|
||
// because they are aware that DDI may succeed in some cases), and a verifier
|
||
// breakpoint would force them to avoid the verifier failure by not enabling
|
||
// verifier.
|
||
//
|
||
if (dxState == PowerDeviceUnspecified ||
|
||
m_SystemWake == PowerSystemUnspecified) {
|
||
DoTraceLevelMessage(
|
||
GetDriverGlobals(), TRACE_LEVEL_WARNING, TRACINGPNP,
|
||
"SystemWake %!SYSTEM_POWER_STATE! and DeviceWake "
|
||
"%!DEVICE_POWER_STATE! power state reported in device "
|
||
"capabilities do not support wake. Both the SystemWake and "
|
||
"DeviceWake members should be nonzero to support wake-up on "
|
||
"this system.", m_SystemWake, dxState);
|
||
}
|
||
|
||
return dxState;
|
||
}
|
||
|