在前面我们用一个实际的案例来说明了INTx中断是如何编程的,但是现在几乎已经很少遇到INTx中断了,基本上都是MSI和MSIX中断。
MSI和MSIX
PCI 2.2 规范中引入了 (MSI) 的消息信号中断,作为基于线路的中断的替代方法。 使用 MSI 的设备通过将值写入特定内存地址来触发中断,而不是使用专用引脚触发中断。 PCI 3.0 定义了一种扩展形式的 MSI,称为 MSI-X,可实现更高的可编程性。 Windows Vista 和更高版本的 Windows 支持 MSI 和 MSI-X。 单个设备可以同时支持 MSI 和 MSI-X。 对于此类设备,操作系统将自动使用 MSI-X。
中断消息是设备写入特定地址以触发中断的特定值。 与基于行(边带信号)的中断不同,消息信号中断具有边缘语义。 设备发送消息,但未收到任何硬件确认,说明已收到中断。
对于 PCI 2.2,消息由地址和部分不透明的 16 位值组成。 为每个设备分配一个地址。 若要发送多条消息,设备可以使用消息值的低 4 位来区分消息。 因此,对于 PCI 2.2,设备最多可以支持 16 条消息。
对于 PCI 3.0,消息由地址和不透明的 32 位值组成。 每封不同的邮件都有其自己的唯一地址。 与 PCI 2.2 不同,设备不会修改值。 对于 PCI 3.0,设备最多可以支持 2,048 条不同的消息。 支持 PCI 3.0 MSI-X 的设备具有一个动态可编程的硬件表,其中包含设备中每个中断源的条目。 可以使用分配给设备的消息之一对此表中的每个条目进行编程,并且可以独立屏蔽。 驱动程序可以将中断消息的编程更改为表条目,以及某个条目是否已被屏蔽。
驱动程序可以注册单个 InterruptMessageService 例程,该例程处理所有可能的消息或每条消息的单个 InterruptService 例程。
驱动程序可以处理设备发送的 MSI,如下所示:
- 在驱动程序安装过程中,在注册表中启用 MSI。 还可以使用注册表指定要为设备分配的消息数;
- (可选)通过响应 IRP_MN_FILTER_RESOURCE_REQUIREMENTS 请求来增加中断消息数并保存一些每条消息设置;
- 在用于IRP_MN_START_DEVICE的驱动程序调度例程中,调用 IoConnectInterruptEx 以注册 InterruptService 或 InterruptMessageService 例程来为设备的中断提供服务。 使用 ioConnectInterruptEx 的 CONNECT_FULLY_SPECIFIED 版本为特定消息注册 InterruptService 例程,或使用 ioConnectInterruptEx 的 CONNECT_MESSAGE_BASED 版本为所有消息注册单个 InterruptMessageService 例程;
- 驱动程序不再打算处理来自设备的中断后,请在禁用设备的中断后调用 IoDisconnectInterruptEx 删除任何已注册的中断服务例程;
设计为使用多个消息的驱动程序应检查分配预期的消息数。 如果即插即用 (PnP) 管理器无法分配请求的消息数,它会只向设备分配一条消息。 驱动程序可以通过以下方式之一检查实际分配的消息数:
- PnP 管理器在其原始资源描述符列表中报告分配的消息数。
- 当 IoConnectInterruptEx 返回时,它将 Parameters-MessageBased.ConnectContext.InterruptMessageTable-MessageCount>> 设置为分配的消息数;
注册表中启用MSI
若要 (MSI) 接收消息信号中断,驱动程序的 INF 文件必须在安装期间在注册表中启用 MSI。 使用设备hardware key的Interrupt Management\MessageSignaledInterruptProperties子项启用 MSI 支持。
Interrupt Management\MessageSignaledInterruptProperties的 MSISupported 条目是一个REG_DWORD值,用于确定设备是否支持 MSI,将 MSISupported 设置为 1 以启用 MSI 支持。
还可以使用注册表指定要为其设备分配的最大 MSI 数。 Interrupt Management\MessageSignaledInterruptProperties 的 MessageNumberLimit 条目是一个REG_DWORD值,指定要分配的最大 MSI 数。
对于从 PCI 2.2开始可用的多消息 MSI ,MessageNumberLimit 必须为 1、2、4、8 或 16。 对于从 PCI 3.0开始可用的 MSI-X 设备 MessageNumberLimit 可以是最多 2,048 个数字。
在驱动程序的 INF 文件中使用 INF AddReg 指令 在设备的硬件密钥下设置注册表项。
下面的代码示例演示如何为设备设置Interrupt Management\MessageSignaledInterruptProperties 下的 MSISupported 条目。 请注意,在添加 MSISupported 值时,AddReg 指令会自动创建Interrupt Management和Interrupt Management\MessageSignaledInterruptProperties:
[mydevice.HW]
AddReg = mydevice_addreg
[mydevice_addreg]
HKR,Interrupt Management\MessageSignaledInterruptProperties,MSISupported,0x00010001,1
中断资源描述符
即插即用 (PnP) 管理器使用两个通道将中断消息分配给设备。
首先,PnP 管理器向驱动程序发送 一个IRP_MN_FILTER_RESOURCE_REQUIREMENTS 请求,其中包含它打算分配给设备的硬件资源列表,包括中断消息。 驱动程序可以修改此列表以更改中断消息数以及一些每条消息设置。 然后,在 PnP 管理器实际分配资源后,它会发送 IRP_MN_START_DEVICE 请求,并提供分配给驱动程序设备的硬件资源(包括中断消息)的完整列表。
IRP_MN_FILTER_RESOURCE_REQUIREMENTS请求提供IO_RESOURCE_DESCRIPTOR结构的列表。 如果设备具有 PCI 2.2 规范中定义的 MSI (消息信号中断) 功能结构,则 PnP 管理器将提供单个中断消息描述符。 如果设备具有 PCI 3.0 规范中定义的 MSI-X 功能结构,则 PnP 管理器为每个中断消息提供一个结构。 中断消息描述符的type = CmResourceTypeInterrupt , Flags = CM_RESOURCE_INTERRUPT_LATCHED |CM_RESOURCE_INTERRUPT_MESSAGE。 驱动程序还可以通过更改 结构的 u.Interrupt 成员来更改中断相关性等设置。 请注意,在使用 MSI 时,中断都具有相同的相关性,而使用 MSI-X 时,它们可能具有不同的相关性。
对于 MSI,如 PCI 2.2 中定义, u.Interrupt.MaximumVector - u.Interrupt.MinimumVector + 1 是为设备分配的中断消息数。 驱动程序可以通过修改 u.Interrupt.MinimumVector 来更改中断消息数。 对于 MSI 中断消息, u.Interrupt.MaximumVector 始终CM_RESOURCE_INTERRUPT_MESSAGE_TOKEN。 若要分配 MessageCount 中断消息,请将 u.Interrupt.MinimumVector 设置为等于CM_RESOURCE_INTERRUPT_MESSAGE_TOKEN - MessageCount + 1。
对于 PCI 3.0 中定义的 MSI-X,驱动程序可以通过在列表中添加或删除条目来更改分配的中断消息数。 请注意,在响应 IRP_MN_START_DEVICE 请求时,不得随后删除以这种方式添加的中断消息资源。 对于 MSI-X,PnP 管理器为每个消息中断提供一个描述符,此描述 符的 u.Interrupt.MinimumVector 和 u.Interrupt.MaximumVector 成员都设置为CM_RESOURCE_INTERRUPT_MESSAGE_TOKEN。
即插即用管理器分配设备的所有硬件资源(包括中断消息)后,会将IRP_MN_START_DEVICE请求发送到驱动程序。 此请求提供两个 CM_PARTIAL_RESOURCE_DESCRIPTOR 结构列表,分别用于原始资源和已翻译的资源。 对于中断消息,PnP 管理器为每个分配的内存地址提供一个结构,type = 为CmResourceTypeInterrupt,Flags = CM_RESOURCE_INTERRUPT_LATCHED |CM_RESOURCE_INTERRUPT_MESSAGE。
请注意,使用 MSI 时,驱动程序仅接收一个中断资源描述符,因为所有消息共享相同的地址。 u.MessageInterrupt.Raw 的 MessageCount 成员可用于确定分配的消息数。 使用 MSI-X 时,驱动程序将接收每个中断消息的单独资源描述符。
在 Windows 8 中,操作系统不支持每个设备功能超过 2048 条中断消息的资源请求。 在 Windows 7 和 Windows Vista 中,操作系统不支持每个设备功能超过 910 条中断消息的资源请求。 如果设备驱动程序超出此限制,设备可能无法启动。 若要使驱动程序能够在包含许多逻辑处理器的计算机中运行,驱动程序应避免为每个处理器请求多个中断。
在系统重新平衡中断资源期间,PnP 管理器可能会要求驱动程序从资源要求列表中选择一组首选的备用中断资源。 但是,PnP 管理器不能始终向驱动程序分配驱动程序首选的资源。 因此,驱动程序必须容忍从资源要求列表中分配任何一组备用中断资源,而不会失败。 例如,为设备分配的消息中断数可能少于驱动程序请求的次数。 在最坏的情况下,驱动程序必须准备好仅使用一个基于线路的中断来操作设备。
动态配置 MSI-X
Windows Vista Service Pack 1 (SP1) 、Windows Server 2008 及更高版本的操作系统支持动态修改 MSI-X 中断消息的属性。 PCI 3.0 规范定义的 MSI-X.PCI 总线驱动程序公开GUID_MSIX_TABLE_CONFIG_INTERFACE接口,以允许 PCI 设备的驱动程序修改总线硬件中断表中的设置。
驱动程序通过向总线驱动程序发送 IRP_MN_QUERY_INTERFACE 请求来使用该接口, 其中 InterfaceType 参数等于 GUID_MSIX_TABLE_CONFIG_INTERFACE。 总线驱动程序提供指向 PCI_MSIX_TABLE_CONFIG_INTERFACE 结构的指针,该结构提供指向修改中断表的三个例程的指针:
- SetTableEntry 将消息 ID 分配给硬件表条目;
- MaskTableEntry 屏蔽与硬件表条目对应的中断;
- UnmaskTableEntry 取消屏蔽对应于硬件表条目的中断;
PCI_MSIX_MASKUNMASK_ENTRY PciMsixMaskmaskEntry;
NTSTATUS PciMsixMaskmaskEntry(
[in] PVOID Context,
[in] ULONG TableEntry
)
{...}
PCI_MSIX_MASKUNMASK_ENTRY PciMsixMaskunmaskEntry;
NTSTATUS PciMsixMaskunmaskEntry(
[in] PVOID Context,
[in] ULONG TableEntry
)
{...}
PCI_MSIX_SET_ENTRY PciMsixSetEntry;
NTSTATUS PciMsixSetEntry(
[in] PVOID Context,
[in] ULONG TableEntry,
[in] ULONG MessageNumber
)
{...}
默认情况下,中断表配置为使第一个条目的消息 ID 为零,第二个条目具有消息 ID 1,依此依此。 如果表条目数超过消息数,则向每个附加表条目分配消息 ID 为零。 (消息 ID 是描述驱动程序的消息信号中断的IO_INTERRUPT_MESSAGE_INFO结构的 MessageInfo 成员中中断项的索引。IoConnectInterruptEx 例程提供指向此结构的指针)。
IoConnectInterruptEx
这个函数比较有意思
#define IoConnectInterruptEx WdmlibIoConnectInterruptEx
NTSTATUS WdmlibIoConnectInterruptEx(
[in, out] PIO_CONNECT_INTERRUPT_PARAMETERS Parameters
);
typedef struct _IO_CONNECT_INTERRUPT_PARAMETERS {
ULONG Version;
union {
IO_CONNECT_INTERRUPT_FULLY_SPECIFIED_PARAMETERS FullySpecified;
IO_CONNECT_INTERRUPT_LINE_BASED_PARAMETERS LineBased;
IO_CONNECT_INTERRUPT_MESSAGE_BASED_PARAMETERS MessageBased;
};
} IO_CONNECT_INTERRUPT_PARAMETERS, *PIO_CONNECT_INTERRUPT_PARAMETERS;
使用 CONNECT_FULLY_SPECIFIED 的 IoConnectInterruptEx
驱动程序可以使用 ioConnectInterruptEx CONNECT_FULLY_SPECIFIED 版本为特定中断注册 InterruptService 例程。 从 Windows Vista 开始,驱动程序可以使用CONNECT_FULLY_SPECIFIED版本。 通过链接到 Iointex.lib 库,驱动程序可以使用 Windows 2000、Windows XP 和 Windows Server 2003 中的 CONNECT_FULLY_SPECIFIED 版本。
驱动程序将 parameters->Version 的值指定为 CONNECT_FULLY_SPECIFIED,并使用 Parameters->FullySpecified 的成员来指定操作的其他参数:
Parameters->FullySpecified.PhysicalDeviceObject 为 ISR 服务的设备指定 PDO。
Parameters->FullySpecified.ServiceRoutine 指向 InterruptService 例程,而 Parameters->FullySpecified。ServiceContext 指定系统作为 ServiceContext 参数传递给 InterruptService 的值。 驱动程序可以使用此来传递上下文信息。
驱动程序提供指向 Parameters-> FullySpecified.InterruptObject中的 PKINTERRUPT 变量的指针。 IoConnectInterruptEx 例程将此变量设置为指向中断的中断对象,该对象可在删除 ISR 时使用。
驱动程序可以选择在 Parameters->FullySpecified.SpinLock 中指定旋转锁,供系统在与 ISR 同步时使用。 大多数驱动程序只需指定 NULL ,使系统能够代表驱动程序分配旋转锁。
驱动程序必须在 Parameters->FullySpecified 的其他成员中指定中断的关键属性。 系统在将 IRP_MN_START_DEVICE IRP 发送到驱动程序时,在 CM_PARTIAL_RESOURCE_DESCRIPTOR 结构数组中提供必要的信息。
系统为每个中断提供一个类型成员等于 CmResourceTypeInterrupt的CM_PARTIAL_RESOURCE_DESCRIPTOR结构。 对于消息信号中断,设置 Flags 成员的CM_RESOURCE_INTERRUPT_MESSAGE位;否则,会将其清除。
CM_PARTIAL_RESOURCE_DESCRIPTOR 的 u.Interrupt 成员包含基于行的中断的说明,而 u.MessageInterrupt.Translated 成员包含消息信号中断的说明:
成员 | 基于行的中断 | 消息信号中断 |
---|---|---|
ShareVector | ShareDisposition | ShareDisposition |
vector | u.Interrupt.Vector | u.MessageInterrupt.Translated.Vector |
Irql | u.Interrupt.Level | u.MessageInterrupt.Translated.Level |
InterruptMode | type & CM_RESOURCE_INTERRUPT_LATCHED | type & CM_RESOURCE_INTERRUPT_LATCHED |
ProcessorEnableMask | u.Interrupt.Affinity | u.MessageInterrupt.Translated.Affinity |
下面是实际的代码:
IO_CONNECT_INTERRUPT_PARAMETERS params;
// deviceExtension is a pointer to the driver's device extension.
// deviceExtension->IntObj is a PKINTERRUPT.
// deviceInterruptService is a pointer to the driver's InterruptService routine.
// IntResource is a CM_PARTIAL_RESOURCE_DESCRIPTOR structure of either type CmResourceTypeInterrupt or CmResourceTypeMessageInterrupt.
// PhysicalDeviceObject is a pointer to the device's PDO.
// ServiceContext is a pointer to driver-specified context for the ISR.
RtlZeroMemory( ¶ms, sizeof(IO_CONNECT_INTERRUPT_PARAMETERS) );
params.Version = CONNECT_FULLY_SPECIFIED;
params.FullySpecified.PhysicalDeviceObject = PhysicalDeviceObject;
params.FullySpecified.InterruptObject = &devExt->IntObj;
params.FullySpecified.ServiceRoutine = deviceInterruptService;
params.FullySpecified.ServiceContext = ServiceContext;
params.FullySpecified.FloatingSave = FALSE;
params.FullySpecified.SpinLock = NULL;
if (IntResource->Flags & CM_RESOURCE_INTERRUPT_MESSAGE) {
// The resource is for a message-signaled interrupt. Use the u.MessageInterrupt.Translated member of IntResource.
params.FullySpecified.Vector = IntResource->u.MessageInterrupt.Translated.Vector;
params.FullySpecified.Irql = (KIRQL)IntResource->u.MessageInterrupt.Translated.Level;
params.FullySpecified.SynchronizeIrql = (KIRQL)IntResource->u.MessageInterrupt.Translated.Level;
params.FullySpecified.ProcessorEnableMask = IntResource->u.MessageInterrupt.Translated.Affinity;
} else {
// The resource is for a line-based interrupt. Use the u.Interrupt member of IntResource.
params.FullySpecified.Vector = IntResource->u.Interrupt.Vector;
params.FullySpecified.Irql = (KIRQL)IntResource->u.Interrupt.Level;
params.FullySpecified.SynchronizeIrql = (KIRQL)IntResource->u.Interrupt.Level;
params.FullySpecified.ProcessorEnableMask = IntResource->u.Interrupt.Affinity;
}
params.FullySpecified.InterruptMode = (IntResource->Flags & CM_RESOURCE_INTERRUPT_LATCHED ? Latched : LevelSensitive);
params.FullySpecified.ShareVector = (BOOLEAN)(IntResource->ShareDisposition == CmResourceShareShared);
status = IoConnectInterruptEx(¶ms);
if (!NT_SUCCESS(status)) {
...
}