找回密码
 立即注册
首页 业界区 业界 PCI9x5x驱动移植支持PCI9054在win7下使用2

PCI9x5x驱动移植支持PCI9054在win7下使用2

威割 2026-1-21 19:35:04
接上文,本文章继续记录中泰联创的数据采集卡驱动翻新过程。
中断初始化部分代码移植

分析PLX9x5x源码可知,中断初始化调用流程如下:
PLxEvtDeviceAdd-PLxInitializeDeviceExtension-PLxInterruptCreate
其中具体初始化代码在PLxInterruptCreate函数中,这部分是纯框架流程无需修改,直接就可以使用:
  1. NTSTATUS
  2. PLxInterruptCreate(
  3.     IN PDEVICE_EXTENSION DevExt
  4.     )
  5. {
  6.     ......
  7. }
复制代码
WdfInterruptCreate函数将创建设备中断对象,后续和中断相关操作都要用到DevExt->Interrupt。
WdfInterruptCreate函数成功返回之后,WDF框架会在系统加载设备时连接中断,连接中断后调用PLxEvtInterruptEnable函数;系统卸载设备时调用PLxEvtInterruptDisable函数后断开中断。
示例程序已经在PLxEvtInterruptEnable函数中对中断寄存器进行了使能操作,这一点PCI9054和PCI9656兼容,所以延用此代码即可,同时需要加上本地总线的中断使能。
  1.         intCSR.bits.LocalIntInputEnable = TRUE;
复制代码
示例程序已经在PLxEvtInterruptDisable函数中对中断寄存器进行了禁止操作,这一点PCI9054和PCI9656兼容,所以延用此代码即可。
  1.         intCSR.bits.LocalIntInputEnable = FALSE;
复制代码
对于中泰联创的老产品上使能和禁止中断操作,则打算转移到应用层进行,这样可以增加驱动的适配性。
中断事件代码部分移植

老驱动在应用层创建事件句柄,传输给内核,在对应中断发生时触发事件句柄,从而简化应用层编程,新驱动需要实现这个功能,
1. 公共定义 (Public.h)

在 Public.h 中定义相关的 IOCTL 和常量:
  1. #define EVENT_COUNT                3L
  2. #define EVENT_SFifo                0L
  3. #define EVENT_ALARM                1L
  4. #define EVENT_TRIP                 2L
  5. #define PCI8KPLX_IOCTL_OPEN_IRQ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS)
  6. #define PCI8KPLX_IOCTL_CLOSE_IRQ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806, METHOD_BUFFERED, FILE_ANY_ACCESS)
复制代码
2. 设备上下文扩展 (Private.h)

在 DEVICE_EXTENSION 结构体中添加用于存储内核事件对象的指针数组:
  1. typedef struct _DEVICE_EXTENSION {
  2.     // ... 现有成员 ...
  3.    
  4.     //用于存储内核事件对象指针
  5.     PKEVENT m_Events[EVENT_COUNT];
  6.    
  7. } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
复制代码
3. IOCTL 处理逻辑 (Control.c)

在 PLxEvtIoDeviceControl 中添加对 PCI8KPLX_IOCTL_OPEN_IRQ和PCI8KPLX_IOCTL_CLOSE_IRQ 的分发:
  1. /**
  2. * 主设备控制入口点
  3. */
  4. VOID
  5. PLxEvtIoDeviceControl(……)
  6. {
  7.     ……
  8.     switch (IoControlCode) {
  9.         case PCI8KPLX_IOCTL_OPEN_IRQ:
  10.             status = PCI8KPLX_IOCTL_OPEN_IRQ_Handler(Request, devExt);
  11.             break;
  12.         case PCI8KPLX_IOCTL_CLOSE_IRQ:
  13.             status = PCI8KPLX_IOCTL_CLOSE_IRQ_Handler(Request, devExt);
  14.             break;
  15.         // ... 其他 case ...
  16.         
  17.         default:
  18.             status = STATUS_INVALID_DEVICE_REQUEST;
  19.             break;
  20.     }
  21.     WdfRequestComplete(Request, status);
  22. }
复制代码
并实现处理函数:
  1. /**
  2. * 功能:将应用层传入的 ULONG 句柄数组转换为内核 PKEVENT 对象数组
  3. * 注意:此方法使用固定大小的数组,适用于事件类型和数量固定的场景。
  4. *      对硬件寄存器的操作放在应用层dll中。
  5. * 兼容性:使用 (HANDLE)(ULONG_PTR)ulH 确保 32 位应用在 64 位系统上的兼容性。
  6. */
  7. NTSTATUS
  8. PCI8KPLX_IOCTL_OPEN_IRQ_Handler(
  9.     _In_ WDFREQUEST Request,
  10.     _In_ PDEVICE_EXTENSION DevExt
  11. )
  12. {
  13.     NTSTATUS status = STATUS_SUCCESS;
  14.     PULONG pBuff = NULL; // 指向输入缓冲区,其中包含 EVENT_COUNT 个 ULONG 句柄
  15.     size_t bufferSize = 0;
  16.     ULONG i;
  17.     // 1. 获取输入缓冲区 (预期包含 EVENT_COUNT 个 ULONG 句柄)
  18.     status = WdfRequestRetrieveInputBuffer(Request, sizeof(ULONG) * EVENT_COUNT, (PVOID*)&pBuff, &bufferSize);
  19.     if (!NT_SUCCESS(status)) {
  20.         return status;
  21.     }
  22.     // 2. 遍历并处理句柄
  23.     for (i = 0; i < EVENT_COUNT; i++) {
  24.         ULONG ulH = pBuff[i];
  25.         HANDLE h = (HANDLE)(ULONG_PTR)ulH; // 关键转换,确保64位兼容性
  26.         if (h != NULL) {
  27.             // 如果该位置已存在引用的事件对象,先释放旧引用
  28.             if (DevExt->m_Events[i] != NULL) {
  29.                 ObDereferenceObject(DevExt->m_Events[i]);
  30.                 DevExt->m_Events[i] = NULL;
  31.             }
  32.             // 将用户态句柄转换为内核事件对象指针
  33.             status = ObReferenceObjectByHandle(
  34.                 h,
  35.                 EVENT_MODIFY_STATE,
  36.                 *ExEventObjectType,
  37.                 UserMode,
  38.                 (PVOID*)&DevExt->m_Events[i],
  39.                 NULL
  40.             );
  41.             if (!NT_SUCCESS(status)) {
  42.                 DevExt->m_Events[i] = NULL;
  43.                 break; // 如果某个句柄转换失败,停止处理并返回错误
  44.             }
  45.         }
  46.     }
  47.     return status;
  48. }
  49. /**
  50. * 事件句柄清理函数 (固定数组版)
  51. * 功能:释放 m_Events 数组中所有存储的事件对象引用,并将指针置为 NULL。
  52. * 注意:此操作会清除所有已注册的事件句柄。
  53. */
  54. NTSTATUS
  55. PCI8KPLX_IOCTL_CLOSE_IRQ_Handler(
  56.     _In_ WDFREQUEST Request,
  57.     _In_ PDEVICE_EXTENSION DevExt
  58. )
  59. {
  60.     NTSTATUS status = STATUS_SUCCESS;
  61.     ULONG i;
  62.     //本函数不需要和应用层交互,所以用不到这个参数
  63.         UNREFERENCED_PARAMETER(Request);
  64.     // 为了保证操作的原子性,可能需要在此处添加设备级的锁,如果 m_Events 访问需要同步的话
  65.     // WdfWaitLockAcquire(DevExt->SomeLock, NULL);
  66.     // 遍历 m_Events 数组
  67.     for (i = 0; i < EVENT_COUNT; i++) {
  68.         if (DevExt->m_Events[i] != NULL) {
  69.             // 释放对该事件对象的引用,防止内存泄漏
  70.             ObDereferenceObject(DevExt->m_Events[i]);
  71.             // 清空指针
  72.             DevExt->m_Events[i] = NULL;
  73.         }
  74.     }
  75.     // WdfWaitLockRelease(DevExt->SomeLock);
  76.     // 此 IOCTL 通常没有输出数据,不需要调用 WdfRequestSetInformation
  77.     // 直接返回成功状态
  78.     return status;
  79. }
复制代码
4. 清理事件防止内存泄漏

在 PlxCleanupDeviceExtension 中进行清理
  1. VOID
  2. PlxCleanupDeviceExtension(
  3.     _In_ PDEVICE_EXTENSION DevExt
  4. )
  5. {
  6.     ULONG i;
  7.     // 遍历并释放事件对象
  8.     for (i = 0; i < EVENT_COUNT; i++) {
  9.         if (DevExt->m_Events[i] != NULL) {
  10.             ObDereferenceObject(DevExt->m_Events[i]);
  11.             DevExt->m_Events[i] = NULL; // 清空指针是个好习惯
  12.         }
  13.     }
  14.     // ... 其他清理代码 ...
  15. }
复制代码
5. 中断触发逻辑 (IsrDpc.c 参考)

在 DPC (如 PLxEvtInterruptDpc) 中,根据硬件状态触发相应的事件:
  1. // 示例:触发 SFIFO 触发值事件
  2. if (DevExt->m_Events[EVENT_SFifo] != NULL) {
  3.     KeSetEvent(DevExt->m_Events[EVENT_SFifo], IO_NO_INCREMENT, FALSE);
  4. }
复制代码
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中声明全局变量
  1. //本来是内核中需要保存的内容,改成应用层保存
  2. ULONG g_ulCtrlWord[MAX_CARD_COUNT];
  3. //为了后面方便阅读代码,将赋值和读取都封装成函数
  4. void SetCtrlWord(ULONG cardNo, ULONG ulCtrlWord) {
  5.         if (cardNo > MAX_CARD_COUNT)
  6.                 return;
  7.         g_ulCtrlWord[cardNo] = ulCtrlWord;
  8. }
  9. ULONG GetCtrlWord(ULONG cardNo) {
  10.         if (cardNo > MAX_CARD_COUNT)
  11.                 return 0;
  12.         return g_ulCtrlWord[cardNo];
  13. }
复制代码
老驱动中有一个InitIRQ函数,只是操作了控制字变量,新驱动将内核操作修改成应用层的变量操作
  1. //改变控制字中的中断相关位       
  2.     unsigned long ulCtrlWord = GetCtrlWord(cardNO);
  3.         ulCtrlWord = SET_ULONG_BITS( ulCtrlWord, IRQ_MASK, IRQ, irqSource );
  4.     SetCtrlWord(cardNO, ulCtrlWord);
复制代码
老驱动中使用OpenIRQ将中断相关事件传递到内核,同时在内核中设置PCI9054的本地控制字使能FPGA中断。新驱动中内核只是接收中断相关事件,然后在应用层使能FPGA中断。
  1. //在DeviceIoControl之后将控制字写入硬件       
  2. //使能之前设置好的中断
  3. WRITED(cardNO, OFF_CTRLWORD, GetCtrlWord(cardNO));
复制代码
接下来要移植应用层访问硬件寄存器的代码

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

2026-1-23 13:31:54

举报

您需要登录后才可以回帖 登录 | 立即注册