春寂 發表於 2026-1-21 17:17:00

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

<p>接上文,本文章继续记录中泰联创的数据采集卡驱动翻新过程。</p>
<h2 id="中断初始化部分代码移植">中断初始化部分代码移植</h2>
<p>分析PLX9x5x源码可知,中断初始化调用流程如下:<br>
PLxEvtDeviceAdd-PLxInitializeDeviceExtension-PLxInterruptCreate<br>
其中具体初始化代码在PLxInterruptCreate函数中,这部分是纯框架流程无需修改,直接就可以使用:</p>
<pre><code class="language-c++">NTSTATUS
PLxInterruptCreate(
    IN PDEVICE_EXTENSION DevExt
    )
{
    ......
}
</code></pre>
<p>WdfInterruptCreate函数将创建设备中断对象,后续和中断相关操作都要用到DevExt-&gt;Interrupt。<br>
WdfInterruptCreate函数成功返回之后,WDF框架会在系统加载设备时连接中断,连接中断后调用PLxEvtInterruptEnable函数;系统卸载设备时调用PLxEvtInterruptDisable函数后断开中断。</p>
<p>示例程序已经在PLxEvtInterruptEnable函数中对中断寄存器进行了使能操作,这一点PCI9054和PCI9656兼容,所以延用此代码即可,同时需要加上本地总线的中断使能。</p>
<pre><code class="language-c++">        intCSR.bits.LocalIntInputEnable = TRUE;
</code></pre>
<p>示例程序已经在PLxEvtInterruptDisable函数中对中断寄存器进行了禁止操作,这一点PCI9054和PCI9656兼容,所以延用此代码即可。</p>
<pre><code class="language-c++">        intCSR.bits.LocalIntInputEnable = FALSE;
</code></pre>
<p>对于中泰联创的老产品上使能和禁止中断操作,则打算转移到应用层进行,这样可以增加驱动的适配性。</p>
<h2 id="中断事件代码部分移植">中断事件代码部分移植</h2>
<p>老驱动在应用层创建事件句柄,传输给内核,在对应中断发生时触发事件句柄,从而简化应用层编程,新驱动需要实现这个功能,</p>
<h3 id="1-公共定义-publich">1. 公共定义 (Public.h)</h3>
<p>在 <code>Public.h</code> 中定义相关的 IOCTL 和常量:</p>
<pre><code class="language-c++">#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)
</code></pre>
<h3 id="2-设备上下文扩展-privateh">2. 设备上下文扩展 (Private.h)</h3>
<p>在 <code>DEVICE_EXTENSION</code> 结构体中添加用于存储内核事件对象的指针数组:</p>
<pre><code class="language-c++">typedef struct _DEVICE_EXTENSION {
    // ... 现有成员 ...
   
    //用于存储内核事件对象指针
    PKEVENT m_Events;
   
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
</code></pre>
<h3 id="3-ioctl-处理逻辑-controlc">3. IOCTL 处理逻辑 (Control.c)</h3>
<p>在 <code>PLxEvtIoDeviceControl</code> 中添加对 <code>PCI8KPLX_IOCTL_OPEN_IRQ</code>和<code>PCI8KPLX_IOCTL_CLOSE_IRQ</code> 的分发:</p>
<pre><code class="language-c++">/**
* 主设备控制入口点
*/
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);
}
</code></pre>
<p>并实现处理函数:</p>
<pre><code class="language-c++">/**
* 功能:将应用层传入的 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*)&amp;pBuff, &amp;bufferSize);
    if (!NT_SUCCESS(status)) {
      return status;
    }

    // 2. 遍历并处理句柄
    for (i = 0; i &lt; EVENT_COUNT; i++) {
      ULONG ulH = pBuff;
      HANDLE h = (HANDLE)(ULONG_PTR)ulH; // 关键转换,确保64位兼容性

      if (h != NULL) {
            // 如果该位置已存在引用的事件对象,先释放旧引用
            if (DevExt-&gt;m_Events != NULL) {
                ObDereferenceObject(DevExt-&gt;m_Events);
                DevExt-&gt;m_Events = NULL;
            }

            // 将用户态句柄转换为内核事件对象指针
            status = ObReferenceObjectByHandle(
                h,
                EVENT_MODIFY_STATE,
                *ExEventObjectType,
                UserMode,
                (PVOID*)&amp;DevExt-&gt;m_Events,
                NULL
            );

            if (!NT_SUCCESS(status)) {
                DevExt-&gt;m_Events = 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-&gt;SomeLock, NULL);

    // 遍历 m_Events 数组
    for (i = 0; i &lt; EVENT_COUNT; i++) {
      if (DevExt-&gt;m_Events != NULL) {
            // 释放对该事件对象的引用,防止内存泄漏
            ObDereferenceObject(DevExt-&gt;m_Events);
            // 清空指针
            DevExt-&gt;m_Events = NULL;
      }
    }

    // WdfWaitLockRelease(DevExt-&gt;SomeLock);

    // 此 IOCTL 通常没有输出数据,不需要调用 WdfRequestSetInformation
    // 直接返回成功状态
    return status;
}
</code></pre>
<h3 id="4-清理事件防止内存泄漏">4. 清理事件防止内存泄漏</h3>
<p>在 PlxCleanupDeviceExtension 中进行清理</p>
<pre><code class="language-c++">VOID
PlxCleanupDeviceExtension(
    _In_ PDEVICE_EXTENSION DevExt
)
{
    ULONG i;

    // 遍历并释放事件对象
    for (i = 0; i &lt; EVENT_COUNT; i++) {
      if (DevExt-&gt;m_Events != NULL) {
            ObDereferenceObject(DevExt-&gt;m_Events);
            DevExt-&gt;m_Events = NULL; // 清空指针是个好习惯
      }
    }

    // ... 其他清理代码 ...
}
</code></pre>
<h3 id="5-中断触发逻辑-isrdpcc-参考">5. 中断触发逻辑 (IsrDpc.c 参考)</h3>
<p>在 DPC (如 <code>PLxEvtInterruptDpc</code>) 中,根据硬件状态触发相应的事件:</p>
<pre><code class="language-c++">// 示例:触发 SFIFO 触发值事件
if (DevExt-&gt;m_Events != NULL) {
    KeSetEvent(DevExt-&gt;m_Events, IO_NO_INCREMENT, FALSE);
}
</code></pre>
<h2 id="5-注意事项">5. 注意事项</h2>
<ol>
<li><strong>64位兼容性</strong>:代码中使用了 <code>(HANDLE)(ULONG_PTR)ulH</code>,确保了 32 位应用程序在 64 位系统下运行时的句柄对齐。</li>
<li><strong>资源释放</strong>:在驱动卸载或设备清理回调(如 <code>PlxEvtDeviceCleanup</code>)中,应遍历 <code>m_Events</code> 并对非空元素调用 <code>ObDereferenceObject</code>,以防止内核内存泄漏。</li>
<li><strong>固定大小限制</strong>:此方案使用固定大小的数组 <code>m_Events</code>,如果应用层需要管理的事件数量超过 <code>EVENT_COUNT</code>,则需要增大该常量并重新编译驱动和应用层。</li>
<li><strong>应用层兼容性</strong>:此方案假定应用层发送的是 <code>ULONG</code> 类型的句柄数组。它主要兼容发送此类数组的32位应用。如果64位应用也发送相同的 <code>ULONG</code> 数组(例如,应用层未区分位数),则其句柄必须是有效的32位兼容值(如WoW64子系统提供的句柄)。</li>
</ol>
<h2 id="应用层要做的和中断相关的事情">应用层要做的和中断相关的事情</h2>
<p>老驱动中使用内核变量来保存一些数据,会增加驱动复杂度,所以都转移到应用层,和中断有关的有控制字,变量,因此在dll中声明全局变量</p>
<pre><code class="language-c++">//本来是内核中需要保存的内容,改成应用层保存
ULONG g_ulCtrlWord;
//为了后面方便阅读代码,将赋值和读取都封装成函数
void SetCtrlWord(ULONG cardNo, ULONG ulCtrlWord) {
        if (cardNo &gt; MAX_CARD_COUNT)
                return;
        g_ulCtrlWord = ulCtrlWord;
}

ULONG GetCtrlWord(ULONG cardNo) {
        if (cardNo &gt; MAX_CARD_COUNT)
                return 0;
        return g_ulCtrlWord;
}
</code></pre>
<p>老驱动中有一个InitIRQ函数,只是操作了控制字变量,新驱动将内核操作修改成应用层的变量操作</p>
<pre><code class="language-c++">//改变控制字中的中断相关位       
    unsigned long ulCtrlWord = GetCtrlWord(cardNO);
        ulCtrlWord = SET_ULONG_BITS( ulCtrlWord, IRQ_MASK, IRQ, irqSource );
    SetCtrlWord(cardNO, ulCtrlWord);
</code></pre>
<p>老驱动中使用OpenIRQ将中断相关事件传递到内核,同时在内核中设置PCI9054的本地控制字使能FPGA中断。新驱动中内核只是接收中断相关事件,然后在应用层使能FPGA中断。</p>
<pre><code class="language-c++">//在DeviceIoControl之后将控制字写入硬件       
//使能之前设置好的中断
WRITED(cardNO, OFF_CTRLWORD, GetCtrlWord(cardNO));
</code></pre>
<p>接下来要移植应用层访问硬件寄存器的代码</p><br><br>
来源:https://www.cnblogs.com/haohaoganhuo/p/19513098
頁: [1]
查看完整版本: PCI9x5x驱动移植支持PCI9054在win7下使用2