在介绍WDF中断之前,有必要建议看一下WDM专栏部分的PCI和中断 ,我们需要知道,无论WDF还是WDM,中断就是中断,处理机制并没有变化,这也是WDF被好评的原因之一,它让一个东西看起来像两个东西一样。
创建中断对象
处理设备硬件中断的 Windows 驱动程序框架 驱动程序必须为每个设备可以支持的每个中断创建框架中断对象。 在操作系统的 Windows 8 或更高版本上运行的框架版本 1.11 及更高版本中,Kernel-Mode Driver Framework (KMDF) 和 User-Mode Driver Framework (UMDF) 驱动程序可以创建需要被动级别处理的中断对象。 但是,除非在芯片 (SoC) 平台上为系统编写驱动程序,否则驱动程序应使用 DIRQL 中断对象。
驱动程序通常在其 EvtDriverDeviceAdd 回调函数中创建框架中断对象。 驱动程序还可以从其 EvtDevicePrepareHardware 回调函数创建中断对象。
框架在即插即用 (PnP) 管理器将系统资源(如中断向量)分配给设备之前,调用驱动程序的 EvtDriverDeviceAdd 回调函数。 PnP 管理器分配资源后,框架会将中断资源存储在设备的中断对象中。 不支持即插即用驱动程序无法使用中断对象。
若要创建框架中断对象,驱动程序必须初始化 WDF_INTERRUPT_CONFIG 结构并将其传递给 WdfInterruptCreate 方法。
UMDF 支持以下类型的中断,注意 UMDF 不支持 共享 边缘触发的中断:
- 电平触发 (共享或独占)
- 仅边缘触发 (独占)
- MSI (定义独占)
从 UMDF 版本 2.15 开始,UMDF 支持简单设备的中断,例如硬件按钮(通常由 GPIO 引脚提供支持),而你无法使用硬件寄存器显式启用或禁用这些中断。 若要支持此类设备,UMDF 驱动程序必须使用边缘触发的独占中断。
从 KMDF 版本 1.15 开始,KMDF 还支持此类设备的中断,而无需 处理Active-Both中断中所述的解决方法。
此外,在 WDF_INTERRUPT_CONFIG 中,驱动程序提供指向以下驱动程序提供的事件回调函数的指针:
- EvtInterruptEnable:启用硬件中断;
- EvtInterruptDisable:禁用硬件中断;
- EvtInterruptIsr:中断服务例程 (中断的 ISR) ;
- EvtInterruptDpc:延迟过程调用中断 (DPC) ;
- EvtInterruptWorkItem:被动级别中断的工作项;
对于在 Windows 8 或更高版本的操作系统上使用框架版本 1.11 或更高版本的驱动程序,驱动程序可以将框架中断对象的父级显式设置为框架设备对象或框架队列对象 (DIRQL 或被动) 。 如果驱动程序指定父级,则驱动程序必须将中断对象的WDF_INTERRUPT_CONFIG结构的 AutomaticSerialization 成员设置为 TRUE。 如果 AutomaticSerialization 为 TRUE,框架会将中断对象的 EvtInterruptDpc 或 EvtInterruptWorkItem 回调函数的执行与来自中断父对象下的其他对象的回调函数同步。
例如,驱动程序可以将队列指定为中断的父级,以将队列的回调与中断的 EvtInterruptDpc 或 EvtInterruptWorkItem 回调同步。 在此配置中,框架在删除设备对象时会删除队列对象。
调用 WdfInterruptCreate 后,驱动程序可以选择调用 WdfInterruptSetPolicy 或 WdfInterruptSetExtendedPolicy 以指定其他中断参数。 通常,驱动程序从其 EvtDriverDeviceAdd 回调函数调用这些方法。
框架在删除中断的父级之前自动删除中断。 (可选)驱动程序可以调用 WdfObjectDelete 以在更早的时间删除中断。
支持消息信号中断
从 Windows Vista 开始,支持消息信号中断 (MSI) 。 若要使操作系统支持设备的 MSI,驱动程序的 INF 文件必须在注册表中设置一些值。
驱动程序应为设备可以支持的每个中断向量或 MSI 消息创建框架中断对象。 如果 PnP 管理器未向设备授予设备可以支持的所有中断资源,则不会使用额外的中断对象,并且不会调用其回调函数。
在 Windows 7 中,操作系统不支持每个设备功能超过 910 条中断消息的资源请求。 在 Windows 8 中,操作系统不支持每个设备功能中断超过 2048 次的资源请求。
如果设备驱动程序超出此限制,设备可能无法启动。 若要在包含多个逻辑处理器的计算机中运行,驱动程序不应为每个处理器请求多个中断。
驱动程序必须容忍系统重新均衡中断资源,其中 PnP 管理员从资源要求列表中分配给设备的任何一组备用中断资源。 例如,向设备分配的消息中断次数可能比请求的驱动程序少。 在最坏的情况下,驱动程序必须准备好仅使用一个基于线路的中断来操作设备。
启用和禁用中断
如果驱动程序处理设备中断,则必须提供启用和禁用中断的 EvtInterruptEnable 和 EvtInterruptDisable 回调函数。 通常,这些回调函数在设备的 DIRQL 上运行,并且必须执行任何必要的操作来启用和禁用设备的中断机制。 对于 被动级中断,这些回调函数以 IRQL = PASSIVE_LEVEL运行,同时持有被动级中断锁。
如果驱动程序必须执行与启用或禁用中断相关的其他操作,并且无法在 IRQL = DIRQL 下执行这些附加操作,则驱动程序还可以提供 EvtDeviceD0EntryPostInterruptsEnabled 和 EvtDeviceD0ExitPreInterruptsDisabled 回调函数。 这两个回调函数在 IRQL = PASSIVE_LEVEL运行,没有保留中断锁,并且可以调用 IRQL = DIRQL 中不可用的框架对象方法。
每次设备进入其工作 (D0) 状态时,框架都会调用驱动程序的 EvtInterruptEnable 和 EvtDeviceD0EntryPostInterruptsEnabled 回调函数,然后框架调用驱动程序的 EvtDeviceD0Entry 回调函数。
每次设备离开工作状态时,框架都会调用驱动程序的 EvtDeviceD0ExitPreInterruptsDisabled 和 EvtInterruptDisable 回调函数,然后框架调用驱动程序的 EvtDeviceD0Exit 回调函数。
不得假定每次框架调用驱动程序的 EvtInterruptEnable 回调函数时,设备都将使用相同的中断资源。 有时,PnP 管理器 会重新分发系统资源,并且可能会将新的中断资源分配给设备。
驱动程序可以调用 WdfInterruptGetInfo 来确定设备的中断资源。 驱动程序可以调用 WdfInterruptGetDevice 来确定中断对象所属的设备。 几个驱动程序可能会调用 WdfInterruptWdmGetInterrupt。
若要直接启用和禁用中断,驱动程序可以调用中断对象的 WdfInterruptEnable 和 WdfInterruptDisable 方法,这些方法调用驱动程序的 EvtInterruptEnable 和 EvtInterruptDisable 事件回调函数。 但是,大多数驱动程序应只允许框架在正确的时间调用 EvtInterruptEnable 和 EvtInterruptDisable 回调函数。
为DIRQL中断提供服务
维护DIRQL中断包括两个步骤,有时还有三个步骤:
- 在 IRQL = DIRQL 运行的中断服务例程中,快速保存易失性信息,例如注册内容;
- 处理延迟过程调用中保存的易失性信息, 在 IRQL = DISPATCH_LEVEL 运行的 DPC;
- 如有必要,请在 IRQL = PASSIVE_LEVEL 执行其他工作;
当设备生成硬件中断时,框架 (ISR) 调用驱动程序的中断服务例程,基于框架的驱动程序将其作为 EvtInterruptIsr 回调函数实现。
在设备的 DIRQL 上运行的 EvtInterruptIsr 回调函数必须快速保存中断信息,如寄存器内容等,这些信息在发生另一个中断时会丢失。
通常, EvtInterruptIsr 回调函数计划延迟的过程调用 (DPC) 稍后在较低的 IRQL = DISPATCH_LEVEL时处理保存的信息。 基于框架的驱动程序将 DPC 例程实现为 EvtInterruptDpc 或 EvtDpcFunc 回调函数。
大多数驱动程序对每种中断类型使用单个 EvtInterruptDpc 回调函数。 若要计划执行 EvtInterruptDpc 回调函数,驱动程序必须从 EvtInterruptIsr 回调函数中调用 WdfInterruptQueueDpcForIsr。
如果驱动程序为每个设备创建多个 框架队列对象 ,则可以考虑为每个队列使用单独的 DPC 对象 和 EvtDpcFunc 回调函数。 若要计划 执行 EvtDpcFunc 回调函数,驱动程序必须首先通过调用 WdfDpcCreate(通常在驱动程序的 EvtDriverDeviceAdd 回调函数中)创建一个或多个 DPC 对象。 然后,驱动程序的 EvtInterruptIsr 回调函数可以调用 WdfDpcEnqueue。
驱动程序通常在其 EvtInterruptDpc 或 EvtDpcFunc 回调函数中完成 I/O 请求。
有时,驱动程序必须在 IRQL = PASSIVE_LEVEL 执行一些中断服务操作。 在这种情况下,驱动程序的 EvtInterruptDpc 或 EvtDpcFunc 回调函数以 IRQL = DISPATCH_LEVEL 执行可以计划执行一个或多个 框架工作项,这些工作项在 IRQL = PASSIVE_LEVEL 运行。
同步中断代码
以下因素使处理多处理器系统上的硬件中断的驱动程序代码复杂化:
- 每次设备中断时,它都会提供特定于中断的信息,这些信息是易失的,因为设备下次中断时可能会被覆盖;
- 设备中断相对较高的 IRQL 及其中断服务例程 (ISR) 可能会中断其他驱动程序代码的执行;
- 对于 DIRQL 中断,ISR 必须在持有驱动程序提供的旋转锁时在 DIRQL 中运行,以便 ISR 可以在保存易失性信息的同时防止其他中断。 DIRQL 可防止当前处理器中断,而旋转锁可防止另一个处理器中断;
- ISR 必须快速运行,因为当 ISR 执行时设备无法中断。 ISR 执行时间过长可能会降低系统速度,或者可能导致数据丢失;
- ISR 和延迟过程调用 (DPC) 例程通常必须访问 ISR 在其中存储设备的易失数据的存储区域。 这些例程必须彼此同步,以便它们不会同时访问存储区域;
由于所有这些因素,在编写处理中断的驱动程序代码时,必须使用以下规则:
1. 只有 EvtInterruptIsr 回调函数访问可变中断数据,例如包含中断信息的设备寄存器。
EvtInterruptIsr 回调函数应将可变数据移动到驱动程序定义的中断数据缓冲区,驱动程序的 EvtInterruptDpc 回调函数、EvtInterruptWorkItem 回调函数或多个 EvtDpcFunc 回调函数可以访问该缓冲区。
如果驱动程序为其中断对象提供 EvtInterruptDpc 或 EvtInterruptWorkItem 回调函数,则存储中断数据的最佳位置是中断对象的 上下文空间。 中断对象的回调函数可以使用接收的对象句柄访问对象的上下文空间。
如果驱动程序为每个 EvtInterruptIsr 回调函数提供多个 EvtDpcFunc 回调函数,则可以在每个 DPC 对象的上下文空间中存储中断数据。
2. 必须同步访问中断数据缓冲区的所有驱动程序代码,以便一次只能有一个例程访问数据。
对于 DIRQL 中断对象, EvtInterruptIsr 回调函数在持有中断对象的驱动程序提供的旋转锁时访问 IRQL = DIRQL 的此数据缓冲区。 因此,访问缓冲区的所有例程也必须在保持旋转锁时在 DIRQL 中运行。 通常,中断的 EvtInterruptDpc 或 EvtDpcFunc 回调函数是唯一必须访问 buffer。
访问中断数据缓冲区的所有例程( EvtInterruptIsr 回调函数除外)都必须执行以下操作之一:
调用 WdfInterruptSynchronize 以计划访问中断数据缓冲区的 EvtInterruptSynchronize 回调函数。
在调用 WdfInterruptAcquireLock 和 WdfInterruptReleaseLock 之间放置访问中断数据缓冲区的代码。
这两种技术都允许 EvtInterruptDpc 或 EvtDpcFunc 函数在保留中断的旋转锁的同时访问 DIRQL 上的中断数据。 DIRQL 可防止当前处理器中断,而旋转锁可防止另一个处理器中断。
如果设备支持多个中断向量或消息,并且要同步驱动程序对这些中断的处理,则可以将单个旋转锁分配给多个 DIRQL 中断对象。 框架确定中断集的最高 DIRQL,并且它始终在该 DIRQL 处获取旋转锁,以便同步代码不会被集中的任何中断向量或消息中断。
对于 被动级中断对象,框架在 IRQL = PASSIVE_LEVEL 调用驱动程序的 EvtInterruptIsr 回调函数之前获取被动级别中断锁。 因此,访问缓冲区的所有例程都必须获取中断锁或在内部同步缓冲区访问。 通常,中断的 EvtInterruptWorkItem 回调函数是唯一访问缓冲区的其他例程。
还可以通过将单个等待锁分配给多个被动级别中断对象来同步驱动程序对多个中断向量的处理。
3. 如果某些处理 DIRQL 中断的代码必须在 IRQL = PASSIVE_LEVEL 运行,则 EvtInterruptDpc 或 EvtDpcFunc 回调函数可以创建一个或多个 工作项 ,以便代码将作为 EvtWorkItem 回调函数运行。
或者,在 KMDF 版本 1.11 及更高版本中,驱动程序可以通过调用 WdfInterruptQueueWorkItemForIsr 来请求中断工作项。 驱动程序的 EvtInterruptIsr 回调函数可以调用 WdfInterruptQueueWorkItemForIsr 或 WdfInterruptQueueDpcForIsr,但不能同时调用两者。
4. 如果可能,请务必将驱动程序的 EvtInterruptDpc 和 EvtDpcFunc 回调函数彼此同步以及与设备关联的其他回调函数,则驱动程序可以在中断的WDF_INTERRUPT_CONFIG结构和 DPC 对象的WDF_DPC_CONFIG结构中将 AutomaticSerialization 成员设置为 TRUE。 或者,驱动程序可以使用 框架旋转锁。 将 AutomaticSerialization 成员设置为 TRUE 不会将 EvtInterruptIsr 回调函数与其他回调函数同步。如前面所述,使用 WdfInterruptSynchronize 或 WdfInterruptAcquireLock 同步 EvtInterruptIsr 回调函数。