博客 / 詳情

返回

PCI9x5x驅動移植支持PCI9054在win7下使用2

接上文,本文章繼續記錄中泰聯創的數據採集卡驅動翻新過程。

中斷初始化部分代碼移植

分析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_IRQPCI8KPLX_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. 注意事項

  1. 64位兼容性:代碼中使用了 (HANDLE)(ULONG_PTR)ulH,確保了 32 位應用程序在 64 位系統下運行時的句柄對齊。
  2. 資源釋放:在驅動卸載或設備清理回調(如 PlxEvtDeviceCleanup)中,應遍歷 m_Events 並對非空元素調用 ObDereferenceObject,以防止內核內存泄漏。
  3. 固定大小限制:此方案使用固定大小的數組 m_Events[EVENT_COUNT],如果應用層需要管理的事件數量超過 EVENT_COUNT,則需要增大該常量並重新編譯驅動和應用層。
  4. 應用層兼容性:此方案假定應用層發送的是 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);

老驅動中使用OpenIRQ將中斷相關事件傳遞到內核,同時在內核中設置PCI9054的本地控制字使能FPGA中斷。新驅動中內核只是接收中斷相關事件,然後在應用層使能FPGA中斷。

//在DeviceIoControl之後將控制字寫入硬件	
//使能之前設置好的中斷
WRITED(cardNO, OFF_CTRLWORD, GetCtrlWord(cardNO));

接下來要移植應用層訪問硬件寄存器的代碼

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.