分析PLX9x5x源码可知中断初始化调用流程如下PLxEvtDeviceAdd-PLxInitializeDeviceExtension-PLxInterruptCreate其中具体初始化代码在PLxInterruptCreate函数中这部分是纯框架流程无需修改直接就可以使用NTSTATUS PLxInterruptCreate( IN PDEVICE_EXTENSION DevExt ) { ...... }WdfInterruptCreate函数将创建设备中断对象后续和中断相关操作都要用到DevExt-Interrupt。WdfInterruptCreate函数成功返回之后WDF框架会在系统加载设备时连接中断连接中断后调用PLxEvtInterruptEnable函数系统卸载设备时调用PLxEvtInterruptDisable函数后断开中断。示例程序已经在PLxEvtInterruptEnable函数中对中断寄存器进行了使能操作这一点PCI9054和PCI9656兼容所以延用此代码即可同时需要加上本地总线的中断使能。intCSR.bits.LocalIntInputEnable TRUE;示例程序已经在PLxEvtInterruptDisable函数中对中断寄存器进行了禁止操作这一点PCI9054和PCI9656兼容所以延用此代码即可。intCSR.bits.LocalIntInputEnable FALSE;对于中泰联创的老产品上使能和禁止中断操作则打算转移到应用层进行这样可以增加驱动的适配性。中断事件代码部分移植老驱动在应用层创建事件句柄传输给内核在对应中断发生时触发事件句柄从而简化应用层编程新驱动需要实现这个功能1. 公共定义 (Public.h)在Public.h中定义相关的 IOCTL 和常量#define EVENT_COUNT 3L #define EVENT_SFifo 0L #define EVENT_ALARM 1L #define EVENT_TRIP 2L #define PCI8KPLX_IOCTL_OPEN_IRQ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS) #define PCI8KPLX_IOCTL_CLOSE_IRQ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806, METHOD_BUFFERED, FILE_ANY_ACCESS)2. 设备上下文扩展 (Private.h)在DEVICE_EXTENSION结构体中添加用于存储内核事件对象的指针数组typedef struct _DEVICE_EXTENSION { // ... 现有成员 ... //用于存储内核事件对象指针 PKEVENT m_Events[EVENT_COUNT]; } DEVICE_EXTENSION, *PDEVICE_EXTENSION;3. IOCTL 处理逻辑 (Control.c)在PLxEvtIoDeviceControl中添加对PCI8KPLX_IOCTL_OPEN_IRQ和PCI8KPLX_IOCTL_CLOSE_IRQ的分发:/** * 主设备控制入口点 */ VOID PLxEvtIoDeviceControl(……) { …… switch (IoControlCode) { case PCI8KPLX_IOCTL_OPEN_IRQ: status PCI8KPLX_IOCTL_OPEN_IRQ_Handler(Request, devExt); break; case PCI8KPLX_IOCTL_CLOSE_IRQ: status PCI8KPLX_IOCTL_CLOSE_IRQ_Handler(Request, devExt); break; // ... 其他 case ... default: status STATUS_INVALID_DEVICE_REQUEST; break; } WdfRequestComplete(Request, status); }并实现处理函数/** * 功能将应用层传入的 ULONG 句柄数组转换为内核 PKEVENT 对象数组 * 注意此方法使用固定大小的数组适用于事件类型和数量固定的场景。 * 对硬件寄存器的操作放在应用层dll中。 * 兼容性使用 (HANDLE)(ULONG_PTR)ulH 确保 32 位应用在 64 位系统上的兼容性。 */ NTSTATUS PCI8KPLX_IOCTL_OPEN_IRQ_Handler( _In_ WDFREQUEST Request, _In_ PDEVICE_EXTENSION DevExt ) { NTSTATUS status STATUS_SUCCESS; PULONG pBuff NULL; // 指向输入缓冲区其中包含 EVENT_COUNT 个 ULONG 句柄 size_t bufferSize 0; ULONG i; // 1. 获取输入缓冲区 (预期包含 EVENT_COUNT 个 ULONG 句柄) status WdfRequestRetrieveInputBuffer(Request, sizeof(ULONG) * EVENT_COUNT, (PVOID*)pBuff, bufferSize); if (!NT_SUCCESS(status)) { return status; } // 2. 遍历并处理句柄 for (i 0; i EVENT_COUNT; i) { ULONG ulH pBuff[i]; HANDLE h (HANDLE)(ULONG_PTR)ulH; // 关键转换确保64位兼容性 if (h ! NULL) { // 如果该位置已存在引用的事件对象先释放旧引用 if (DevExt-m_Events[i] ! NULL) { ObDereferenceObject(DevExt-m_Events[i]); DevExt-m_Events[i] NULL; } // 将用户态句柄转换为内核事件对象指针 status ObReferenceObjectByHandle( h, EVENT_MODIFY_STATE, *ExEventObjectType, UserMode, (PVOID*)DevExt-m_Events[i], NULL ); if (!NT_SUCCESS(status)) { DevExt-m_Events[i] NULL; break; // 如果某个句柄转换失败停止处理并返回错误 } } } return status; } /** * 事件句柄清理函数 (固定数组版) * 功能释放 m_Events 数组中所有存储的事件对象引用并将指针置为 NULL。 * 注意此操作会清除所有已注册的事件句柄。 */ NTSTATUS PCI8KPLX_IOCTL_CLOSE_IRQ_Handler( _In_ WDFREQUEST Request, _In_ PDEVICE_EXTENSION DevExt ) { NTSTATUS status STATUS_SUCCESS; ULONG i; //本函数不需要和应用层交互所以用不到这个参数 UNREFERENCED_PARAMETER(Request); // 为了保证操作的原子性可能需要在此处添加设备级的锁如果 m_Events 访问需要同步的话 // WdfWaitLockAcquire(DevExt-SomeLock, NULL); // 遍历 m_Events 数组 for (i 0; i EVENT_COUNT; i) { if (DevExt-m_Events[i] ! NULL) { // 释放对该事件对象的引用防止内存泄漏 ObDereferenceObject(DevExt-m_Events[i]); // 清空指针 DevExt-m_Events[i] NULL; } } // WdfWaitLockRelease(DevExt-SomeLock); // 此 IOCTL 通常没有输出数据不需要调用 WdfRequestSetInformation // 直接返回成功状态 return status; }4. 清理事件防止内存泄漏在 PlxCleanupDeviceExtension 中进行清理VOID PlxCleanupDeviceExtension( _In_ PDEVICE_EXTENSION DevExt ) { ULONG i; // 遍历并释放事件对象 for (i 0; i EVENT_COUNT; i) { if (DevExt-m_Events[i] ! NULL) { ObDereferenceObject(DevExt-m_Events[i]); DevExt-m_Events[i] NULL; // 清空指针是个好习惯 } } // ... 其他清理代码 ... }5. 中断触发逻辑 (IsrDpc.c 参考)在 DPC (如PLxEvtInterruptDpc) 中根据硬件状态触发相应的事件// 示例触发 SFIFO 触发值事件 if (DevExt-m_Events[EVENT_SFifo] ! NULL) { KeSetEvent(DevExt-m_Events[EVENT_SFifo], IO_NO_INCREMENT, FALSE); }5. 注意事项64位兼容性代码中使用了(HANDLE)(ULONG_PTR)ulH确保了 32 位应用程序在 64 位系统下运行时的句柄对齐。资源释放在驱动卸载或设备清理回调如PlxEvtDeviceCleanup中应遍历m_Events并对非空元素调用ObDereferenceObject以防止内核内存泄漏。固定大小限制此方案使用固定大小的数组m_Events[EVENT_COUNT]如果应用层需要管理的事件数量超过EVENT_COUNT则需要增大该常量并重新编译驱动和应用层。应用层兼容性此方案假定应用层发送的是ULONG类型的句柄数组。它主要兼容发送此类数组的32位应用。如果64位应用也发送相同的ULONG数组例如应用层未区分位数则其句柄必须是有效的32位兼容值如WoW64子系统提供的句柄。应用层要做的和中断相关的事情老驱动中使用内核变量来保存一些数据会增加驱动复杂度所以都转移到应用层和中断有关的有控制字变量因此在dll中声明全局变量//本来是内核中需要保存的内容改成应用层保存 ULONG g_ulCtrlWord[MAX_CARD_COUNT]; //为了后面方便阅读代码将赋值和读取都封装成函数 void SetCtrlWord(ULONG cardNo, ULONG ulCtrlWord) { if (cardNo MAX_CARD_COUNT) return; g_ulCtrlWord[cardNo] ulCtrlWord; } ULONG GetCtrlWord(ULONG cardNo) { if (cardNo MAX_CARD_COUNT) return 0; return g_ulCtrlWord[cardNo]; }老驱动中有一个InitIRQ函数只是操作了控制字变量新驱动将内核操作修改成应用层的变量操作//改变控制字中的中断相关位 unsigned long ulCtrlWord GetCtrlWord(cardNO); ulCtrlWord SET_ULONG_BITS( ulCtrlWord, IRQ_MASK, IRQ, irqSource ); SetCtrlWord(cardNO, ulCtrlWord);
中断初始化部分代码移植
分析PLX9x5x源码可知中断初始化调用流程如下PLxEvtDeviceAdd-PLxInitializeDeviceExtension-PLxInterruptCreate其中具体初始化代码在PLxInterruptCreate函数中这部分是纯框架流程无需修改直接就可以使用NTSTATUS PLxInterruptCreate( IN PDEVICE_EXTENSION DevExt ) { ...... }WdfInterruptCreate函数将创建设备中断对象后续和中断相关操作都要用到DevExt-Interrupt。WdfInterruptCreate函数成功返回之后WDF框架会在系统加载设备时连接中断连接中断后调用PLxEvtInterruptEnable函数系统卸载设备时调用PLxEvtInterruptDisable函数后断开中断。示例程序已经在PLxEvtInterruptEnable函数中对中断寄存器进行了使能操作这一点PCI9054和PCI9656兼容所以延用此代码即可同时需要加上本地总线的中断使能。intCSR.bits.LocalIntInputEnable TRUE;示例程序已经在PLxEvtInterruptDisable函数中对中断寄存器进行了禁止操作这一点PCI9054和PCI9656兼容所以延用此代码即可。intCSR.bits.LocalIntInputEnable FALSE;对于中泰联创的老产品上使能和禁止中断操作则打算转移到应用层进行这样可以增加驱动的适配性。中断事件代码部分移植老驱动在应用层创建事件句柄传输给内核在对应中断发生时触发事件句柄从而简化应用层编程新驱动需要实现这个功能1. 公共定义 (Public.h)在Public.h中定义相关的 IOCTL 和常量#define EVENT_COUNT 3L #define EVENT_SFifo 0L #define EVENT_ALARM 1L #define EVENT_TRIP 2L #define PCI8KPLX_IOCTL_OPEN_IRQ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS) #define PCI8KPLX_IOCTL_CLOSE_IRQ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806, METHOD_BUFFERED, FILE_ANY_ACCESS)2. 设备上下文扩展 (Private.h)在DEVICE_EXTENSION结构体中添加用于存储内核事件对象的指针数组typedef struct _DEVICE_EXTENSION { // ... 现有成员 ... //用于存储内核事件对象指针 PKEVENT m_Events[EVENT_COUNT]; } DEVICE_EXTENSION, *PDEVICE_EXTENSION;3. IOCTL 处理逻辑 (Control.c)在PLxEvtIoDeviceControl中添加对PCI8KPLX_IOCTL_OPEN_IRQ和PCI8KPLX_IOCTL_CLOSE_IRQ的分发:/** * 主设备控制入口点 */ VOID PLxEvtIoDeviceControl(……) { …… switch (IoControlCode) { case PCI8KPLX_IOCTL_OPEN_IRQ: status PCI8KPLX_IOCTL_OPEN_IRQ_Handler(Request, devExt); break; case PCI8KPLX_IOCTL_CLOSE_IRQ: status PCI8KPLX_IOCTL_CLOSE_IRQ_Handler(Request, devExt); break; // ... 其他 case ... default: status STATUS_INVALID_DEVICE_REQUEST; break; } WdfRequestComplete(Request, status); }并实现处理函数/** * 功能将应用层传入的 ULONG 句柄数组转换为内核 PKEVENT 对象数组 * 注意此方法使用固定大小的数组适用于事件类型和数量固定的场景。 * 对硬件寄存器的操作放在应用层dll中。 * 兼容性使用 (HANDLE)(ULONG_PTR)ulH 确保 32 位应用在 64 位系统上的兼容性。 */ NTSTATUS PCI8KPLX_IOCTL_OPEN_IRQ_Handler( _In_ WDFREQUEST Request, _In_ PDEVICE_EXTENSION DevExt ) { NTSTATUS status STATUS_SUCCESS; PULONG pBuff NULL; // 指向输入缓冲区其中包含 EVENT_COUNT 个 ULONG 句柄 size_t bufferSize 0; ULONG i; // 1. 获取输入缓冲区 (预期包含 EVENT_COUNT 个 ULONG 句柄) status WdfRequestRetrieveInputBuffer(Request, sizeof(ULONG) * EVENT_COUNT, (PVOID*)pBuff, bufferSize); if (!NT_SUCCESS(status)) { return status; } // 2. 遍历并处理句柄 for (i 0; i EVENT_COUNT; i) { ULONG ulH pBuff[i]; HANDLE h (HANDLE)(ULONG_PTR)ulH; // 关键转换确保64位兼容性 if (h ! NULL) { // 如果该位置已存在引用的事件对象先释放旧引用 if (DevExt-m_Events[i] ! NULL) { ObDereferenceObject(DevExt-m_Events[i]); DevExt-m_Events[i] NULL; } // 将用户态句柄转换为内核事件对象指针 status ObReferenceObjectByHandle( h, EVENT_MODIFY_STATE, *ExEventObjectType, UserMode, (PVOID*)DevExt-m_Events[i], NULL ); if (!NT_SUCCESS(status)) { DevExt-m_Events[i] NULL; break; // 如果某个句柄转换失败停止处理并返回错误 } } } return status; } /** * 事件句柄清理函数 (固定数组版) * 功能释放 m_Events 数组中所有存储的事件对象引用并将指针置为 NULL。 * 注意此操作会清除所有已注册的事件句柄。 */ NTSTATUS PCI8KPLX_IOCTL_CLOSE_IRQ_Handler( _In_ WDFREQUEST Request, _In_ PDEVICE_EXTENSION DevExt ) { NTSTATUS status STATUS_SUCCESS; ULONG i; //本函数不需要和应用层交互所以用不到这个参数 UNREFERENCED_PARAMETER(Request); // 为了保证操作的原子性可能需要在此处添加设备级的锁如果 m_Events 访问需要同步的话 // WdfWaitLockAcquire(DevExt-SomeLock, NULL); // 遍历 m_Events 数组 for (i 0; i EVENT_COUNT; i) { if (DevExt-m_Events[i] ! NULL) { // 释放对该事件对象的引用防止内存泄漏 ObDereferenceObject(DevExt-m_Events[i]); // 清空指针 DevExt-m_Events[i] NULL; } } // WdfWaitLockRelease(DevExt-SomeLock); // 此 IOCTL 通常没有输出数据不需要调用 WdfRequestSetInformation // 直接返回成功状态 return status; }4. 清理事件防止内存泄漏在 PlxCleanupDeviceExtension 中进行清理VOID PlxCleanupDeviceExtension( _In_ PDEVICE_EXTENSION DevExt ) { ULONG i; // 遍历并释放事件对象 for (i 0; i EVENT_COUNT; i) { if (DevExt-m_Events[i] ! NULL) { ObDereferenceObject(DevExt-m_Events[i]); DevExt-m_Events[i] NULL; // 清空指针是个好习惯 } } // ... 其他清理代码 ... }5. 中断触发逻辑 (IsrDpc.c 参考)在 DPC (如PLxEvtInterruptDpc) 中根据硬件状态触发相应的事件// 示例触发 SFIFO 触发值事件 if (DevExt-m_Events[EVENT_SFifo] ! NULL) { KeSetEvent(DevExt-m_Events[EVENT_SFifo], IO_NO_INCREMENT, FALSE); }5. 注意事项64位兼容性代码中使用了(HANDLE)(ULONG_PTR)ulH确保了 32 位应用程序在 64 位系统下运行时的句柄对齐。资源释放在驱动卸载或设备清理回调如PlxEvtDeviceCleanup中应遍历m_Events并对非空元素调用ObDereferenceObject以防止内核内存泄漏。固定大小限制此方案使用固定大小的数组m_Events[EVENT_COUNT]如果应用层需要管理的事件数量超过EVENT_COUNT则需要增大该常量并重新编译驱动和应用层。应用层兼容性此方案假定应用层发送的是ULONG类型的句柄数组。它主要兼容发送此类数组的32位应用。如果64位应用也发送相同的ULONG数组例如应用层未区分位数则其句柄必须是有效的32位兼容值如WoW64子系统提供的句柄。应用层要做的和中断相关的事情老驱动中使用内核变量来保存一些数据会增加驱动复杂度所以都转移到应用层和中断有关的有控制字变量因此在dll中声明全局变量//本来是内核中需要保存的内容改成应用层保存 ULONG g_ulCtrlWord[MAX_CARD_COUNT]; //为了后面方便阅读代码将赋值和读取都封装成函数 void SetCtrlWord(ULONG cardNo, ULONG ulCtrlWord) { if (cardNo MAX_CARD_COUNT) return; g_ulCtrlWord[cardNo] ulCtrlWord; } ULONG GetCtrlWord(ULONG cardNo) { if (cardNo MAX_CARD_COUNT) return 0; return g_ulCtrlWord[cardNo]; }老驱动中有一个InitIRQ函数只是操作了控制字变量新驱动将内核操作修改成应用层的变量操作//改变控制字中的中断相关位 unsigned long ulCtrlWord GetCtrlWord(cardNO); ulCtrlWord SET_ULONG_BITS( ulCtrlWord, IRQ_MASK, IRQ, irqSource ); SetCtrlWord(cardNO, ulCtrlWord);