使用中断唤醒设备
当设备转换为低功耗状态时,框架会断开连接 (或报告为非活动) 用于 I/O 处理的中断。 从在 Windows 8.1 上运行的 KMDF 1.13 和 UMDF 2.0 开始,WDF 驱动程序可以创建一个框架中断对象,该对象在设备转换为低功耗状态时保持活动状态,然后可用于唤醒设备并将其还原到完全处于 D0 状态。
如果要为芯片上的系统开发 WDF 驱动程序 (SoC) 平台,则可以使用此类中断唤醒不提供传统唤醒信号机制的设备。 若要使用此功能,设备必须具有通过 ACPI 公开的唤醒中断的硬件支持。 创建中断的驱动程序必须是设备的电源策略所有者。
当设备转换为低功耗状态时,框架不会断开已标识为支持唤醒的中断。 当设备中断时,框架在 IRQL = PASSIVE_LEVEL调用驱动程序的 EvtDeviceD0Entry 和 EvtInterruptIsr 回调例程。
如果驱动程序已创建被动 级中断对象 用于 I/O 处理,我们建议共享同一个中断对象以用于唤醒功能。 在此方案中,驱动程序的 EvtInterruptIsr 回调例程实现条件逻辑以执行 I/O 相关中断的处理以及唤醒处理。
但是,如果驱动程序使用的中断需要在设备的 IRQL (DIRQL) 进行处理,我们建议创建额外的框架中断对象以提供唤醒功能。
按照以下步骤在 KMDF 或 UMDF 驱动程序中创建支持唤醒的中断对象:
1. 调用 WdfDeviceAssignS0IdleSettings,通常来自 EvtDriverDeviceAdd,并在 IdleCaps 参数中指定 IdleCanWakeFromS0;
2. (可选)调用 WdfDeviceInitSetPowerPolicyEventCallbacks 以注册 支持系统唤醒中所述的事件回调函数;
3. 调用 WDF_INTERRUPT_CONFIG_INIT 以初始化 WDF_INTERRUPT_CONFIG 结构。 提供在被动级别调用的 EvtInterruptIsr 回调函数。 在配置结构中,将 PassiveHandling 和 CanWakeDevice 设置为 TRUE。 然后从驱动程序的 EvtDevicePrepareHardware 回调函数调用 WdfInterruptCreate 以创建框架中断对象;
4. 调用 WdfDeviceAssignSxWakeSettings 将设备配置为将系统从低功耗状态唤醒;
WDF_DEVICE_POWER_POLICY_WAKE_SETTINGS_INIT(&wakeSettings);
wakeSettings.DxState = PowerDeviceD3;
wakeSettings.UserControlOfWakeSettings = WakeDoNotAllowUserControl;
wakeSettings.Enabled = WdfTrue;status = WdfDeviceAssignSxWakeSettings(Device, &wakeSettings);
if (!NT_SUCCESS(status)) {Trace(TRACE_LEVEL_ERROR,"WdfDeviceAssignSxWakeSettings failed %x\n", status);return status;
}
5. 当设备转换为低功耗状态时,框架不会为支持唤醒的中断调用 EvtInterruptDisable 。 如果驱动程序提供了 EvtDeviceArmWakeFromS0 ,框架会调用该框架;
6. 当设备发出唤醒中断信号时,框架会调用驱动程序的 EvtDeviceD0Entry 回调例程;
7. 如果驱动程序的 EvtDeviceD0Entry 回调返回成功,框架将在被动级别调用驱动程序的 EvtInterruptIsr 回调。 在中断处理程序返回之前,它必须在中断控制器中静音中断。 如果驱动程序从 EvtDeviceD0Entry 返回失败代码,框架将断开中断,并调用驱动程序的 EvtInterruptDisable 回调,如果驱动程序已提供;
8. 如果驱动程序提供了以下唤醒事件回调例程,则框架会调用以下唤醒事件回调例程:
EvtDeviceDisarmWakeFromS0
EvtDeviceDisarmWakeFromSx
EvtDeviceWakeFromS0Triggered
EvtDeviceWakeFromSxTriggered
9. 框架继续执行正常的通电回调序列;
可以在windbg中使用 !wdfkd.wdfinterrupt 调试器扩展来显示特定中断是否已配置为支持唤醒。
注意: 唤醒中断功能不能与 USB 选择性挂起结合使用。
处理同时处于活动状态的中断
注意仅适用于 Kernel-Mode Driver Framework (KMDF) 版本 1.13 及更早版本。
许多设备都有控制中断生成和屏蔽的硬件寄存器。 通常,此类设备的 KMDF 和 UMDF 驱动程序使用框架的内置中断支持。
但是,芯片上的系统上的简单设备 (SoC) 硬件平台可能没有用于中断的硬件寄存器。 因此,此类设备的驱动程序可能无法控制何时生成中断,也无法在硬件中屏蔽中断。 如果设备在连接后立即中断,并且驱动程序正在使用框架的中断支持,则有可能在框架完全初始化框架中断对象之前触发中断。 因此,KMDF 驱动程序必须直接调用 WDM 例程才能连接和断开中断。 由于 UMDF 驱动程序无法调用这些方法,因此不能为此类设备编写 UMDF 驱动程序。
在 SoC 硬件平台上,主动两个中断通常用于非常简单的设备,如硬件按钮。 当用户按下一键时,设备的中断信号线将从低到高或从高转换为低。 当用户松开一键时,中断线会向相反的方向转换。 配置为双活动中断输入的 GPIO 引脚在从低到高和高到低转换时生成中断,导致系统在这两种情况下调用外围设备驱动程序的中断服务例程 ISR。 但是,驱动程序不会收到转换是低到高还是高到低转换的指示。
若要区分从低到高以及从高到低的转换,驱动程序必须跟踪每个中断的状态。 为此,驱动程序可能会维护布尔中断状态值,当中断行状态较低时为 FALSE ,当行状态较高时为 TRUE 。
假设系统启动时行状态默认为低。 驱动程序在其 EvtDevicePrepareHardware 回调函数中将状态值初始化为 FALSE。 然后,每次调用驱动程序的 ISR指示状态发生更改时,驱动程序都会反转其 ISR 中的状态值。
如果系统启动时线路状态较高,则启用中断后会立即触发。 由于驱动程序直接调用 IoConnectInterruptEx 例程,而不是调用 WdfInterruptCreate,因此可确保它收到可能的即时中断。
此解决方案要求 GPIO 控制器支持硬件中的双主动中断,或者 GPIO 控制器的驱动程序在软件中模拟双主动中断。
下面的代码示例演示外围设备的 KMDF 驱动程序如何跟踪中断极性。
typedef struct _INTERRUPT_CONTEXT INTERRUPT_CONTEXT, *PINTERRUPT_CONTEXT;
typedef struct _DEVICE_CONTEXT DEVICE_CONTEXT, *PDEVICE_CONTEXT;struct _INTERRUPT_CONTEXT
{BOOLEAN State;PDEVICE_CONTEXT DeviceContext;
};struct _DEVICE_CONTEXT
{PKINTERRUPT Interrupt;INTERRUPT_CONTEXT InterruptContext;PDEVICE_OBJECT PhysicalDeviceObject;KSPIN_LOCK SpinLock;
};...BOOLEAN
YourInterruptIsr(__in PKINTERRUPT Interrupt,__in PVOID ServiceContext)
{PINTERRUPT_CONTEXT InterruptContext = (PINTERRUPT_CONTEXT)ServiceContext;PDEVICE_CONTEXT DeviceContext = InterruptContext->DeviceContext;//// Flip the state.//InterruptContext->State = !InterruptContext->State;IoRequestDpc(DeviceContext->PhysicalDeviceObject, DeviceContext->PhysicalDeviceObject->CurrentIrp, InterruptContext);
}VOID
YourInterruptDpc(__in PKDPC Dpc,__in PDEVICE_OBJECT DeviceObject,__inout PIRP Irp,__in_opt PVOID ContextPointer)
{PINTERRUPT_CONTEXT InterruptContext = (PINTERRUPT_CONTEXT)ContextPointer;...
}NTSTATUS
EvtDriverDeviceAdd(__in WDFDRIVER Driver,__in PWDFDEVICE_INIT DeviceInit)
{WDFDEVICE Device;PDEVICE_CONTEXT DeviceContext;...DeviceContext->Interrupt = NULL;DeviceContext->PhysicalDeviceObject = WdfDeviceWdmGetPhysicalDevice(Device);KeInitializeSpinLock(&DeviceContext->SpinLock);IoInitializeDpcRequest(DeviceContext->PhysicalDeviceObject, YourInterruptDpc);
}NTSTATUS
EvtDevicePrepareHardware(__in WDFDEVICE Device,__in WDFCMRESLIST ResourcesRaw,__in WDFCMRESLIST ResourcesTranslated)
{PDEVICE_CONTEXT DeviceContext = YourGetDeviceContext(Device);for (ULONG i = 0; i < WdfCmResourceListGetCount(ResourcesTranslated); i++){PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, i);if (descriptor->Type == CmResourceTypeInterrupt){IO_CONNECT_INTERRUPT_PARAMETERS params;RtlZeroMemory(¶ms, sizeof(params));params.Version = CONNECT_FULLY_SPECIFIED;params.FullySpecified.PhysicalDeviceObject = DeviceContext->PhysicalDeviceObject;params.FullySpecified.InterruptObject = &DeviceContext->Interrupt;params.FullySpecified.ServiceRoutine = YourInterruptIsr;params.FullySpecified.ServiceContext = (PVOID)&DeviceContext->InterruptContext;params.FullySpecified.SpinLock = &DeviceContext->SpinLock;params.FullySpecified.Vector = descriptor->u.Interrupt.Vector;params.FullySpecified.Irql = (KIRQL)descriptor->u.Interrupt.Level;params.FullySpecified.SynchronizeIrql = (KIRQL)descriptor->u.Interrupt.Level;params.FullySpecified.InterruptMode = (descriptor->Flags & CM_RESOURCE_INTERRUPT_LATCHED) ? Latched : LevelSensitive;params.FullySpecified.ProcessorEnableMask = descriptor->u.Interrupt.Affinity;params.FullySpecified.ShareVector = descriptor->ShareDisposition;//// Default state is low.//DeviceContext->InterruptContext.State = 0;DeviceContext->InterruptContext.DeviceContext = DeviceContext;return IoConnectInterruptEx(¶ms);}}return STATUS_SUCCESS;
}NTSTATUS
EvtDeviceReleaseHardware(__in WDFDEVICE Device,__in WDFCMRESLIST ResourcesTranslated
)
{PDEVICE_CONTEXT DeviceContext = YourGetDeviceContext(Device);if (NULL != DeviceContext->Interrupt){IO_DISCONNECT_INTERRUPT_PARAMETERS params;params.Version = CONNECT_FULLY_SPECIFIED;params.ConnectionContext.InterruptObject = DeviceContext->Interrupt;IoDisconnectInterruptEx(¶ms);}return STATUS_SUCCESS;
}
在前面的代码示例中,驱动程序的 EvtDriverDeviceAdd 回调函数配置设备上下文,然后调用 IoInitializeDpcRequest 来注册 DpcForIsr 例程。
驱动程序的 InterruptService 例程反转中断状态值,然后调用 IoRequestDpc 将 DPC 排队。
在其 EvtDevicePrepareHardware 回调函数中,驱动程序将状态值初始化为 FALSE ,然后调用 IoConnectInterruptEx。 在其 EvtDeviceReleaseHardware 回调函数中,驱动程序调用 IoDisconnectInterruptEx 以取消注册其 ISR。