在编写PCI9054 WDF驱动过程中,参考PLX9x5x_PCI_Driver示例程序,让Qoder给我生成了一个完整的中断执行流程,大家想要尝试Qoder可以在官网下载,也可以通过下面链接注册下载:
Qoder下载链接
以下为Qoder生成内容:
本文梳理 PLX9x5x_PCI_Driver内核驱动源码,中断从注册 到被硬件触发 再到完成上层 I/O 请求的完整链路,方便后续查阅与调试。
1. 驱动加载阶段:注册设备与回调(还未触及中断)
1.1 DriverEntry
文件:Pci9656.c
- 调用
WDF_DRIVER_CONFIG_INIT(&config, PLxEvtDeviceAdd),将PLxEvtDeviceAdd注册为 EvtDeviceAdd 回调。 - 调用
WdfDriverCreate创建WDFDRIVER对象。
作用:告诉 WDF 框架"以后有新设备枚举时,请调用
PLxEvtDeviceAdd来创建设备对象和相关资源"。
1.2 PLxEvtDeviceAdd ------ 设备对象与扩展创建
文件:Pci9656.c
主要步骤:
-
填充即将使用到的 PnP / 电源管理回调:
EvtDevicePrepareHardware = PLxEvtDevicePrepareHardwareEvtDeviceReleaseHardware = PLxEvtDeviceReleaseHardwareEvtDeviceD0Entry = PLxEvtDeviceD0EntryEvtDeviceD0Exit = PLxEvtDeviceD0Exit
-
设置设备级同步范围:
attributes.SynchronizationScope = WdfSynchronizationScopeDevice- 含义:与该
WDFDEVICE直接关联的对象的回调(队列回调、PLxEvtInterruptDpc等)会被框架用一个内部"设备锁"串行化,减少显式自旋锁的需求。
-
调用
WdfDeviceCreate(&DeviceInit, &attributes, &device)创建WDFDEVICE。 -
获取并初始化
DEVICE_EXTENSION:- 通过
PLxGetDeviceContext(device)得到devExt; - 保存
devExt->Device = device。
- 通过
-
为该设备创建接口:
- 通过
WdfDeviceCreateDeviceInterface(device, &GUID_PLX_INTERFACE, NULL),给用户态应用提供符号链接接口。
- 通过
-
设置电源管理策略:
PLxSetIdleAndWakeSettings(devExt)。
-
设备扩展初始化(非常关键):
- 调用
PLxInitializeDeviceExtension(devExt):- 创建读写/控制队列;
- 创建 DMA 相关对象;
- 创建中断对象
WDFINTERRUPT(中断"注册"的核心)。
- 调用
2. 设备扩展初始化:创建 WDFINTERRUPT(中断"注册")
2.1 PLxInitializeDeviceExtension
文件:Init.c
该函数在 PLxEvtDeviceAdd 中被调用,主要职责:
-
设置
MaximumTransferLength,计算 DMA 需要的DMA_TRANSFER_ELEMENT数量。 -
创建三个 I/O 队列:
WriteQueue:顺序队列,绑定PLxEvtIoWrite;ReadQueue:顺序队列,绑定PLxEvtIoRead;ControlQueue:顺序队列,绑定PLxEvtIoDeviceControl。
-
为不同的
WdfRequestType配置请求分发到对应队列。 -
创建中断对象 :
cstatus = PLxInterruptCreate(DevExt); -
初始化 DMA:
cstatus = PLxInitializeDMA(DevExt);
2.2 PLxInterruptCreate ------ 配置并创建 WDFINTERRUPT
文件:IsrDpc.c
核心逻辑:
-
初始化
WDF_INTERRUPT_CONFIG:cWDF_INTERRUPT_CONFIG_INIT( &InterruptConfig, PLxEvtInterruptIsr, // ISR 回调 PLxEvtInterruptDpc // DPC 回调 ); InterruptConfig.EvtInterruptEnable = PLxEvtInterruptEnable; InterruptConfig.EvtInterruptDisable = PLxEvtInterruptDisable; // 测试 ISR/DPC 同步 InterruptConfig.AutomaticSerialization = TRUE;说明:
PLxEvtInterruptIsr:真正的中断服务例程,运行在 DIRQL;PLxEvtInterruptDpc:由 ISR 排队的 DPC,运行在较低 IRQL,用来完成 DMA 事务和 I/O 请求;PLxEvtInterruptEnable/PLxEvtInterruptDisable:连接/断开中断线时调用,用来开/关设备中断使能;AutomaticSerialization = TRUE:框架会自动对 ISR 和 DPC 做序列化处理,简化同步逻辑。
-
调用
WdfInterruptCreate:cstatus = WdfInterruptCreate( DevExt->Device, &InterruptConfig, WDF_NO_OBJECT_ATTRIBUTES, &DevExt->Interrupt );成功后:
- 已在 WDF 层 注册好 ISR 和 DPC;
- 拿到中断对象句柄
DevExt->Interrupt,后续可用于:WdfInterruptQueueDpcForIsr(DevExt->Interrupt)排队 DPC;WdfInterruptAcquireLock(DevExt->Interrupt)/WdfInterruptReleaseLock做精细同步。
注意:此时只是"软件上注册了 ISR/DPC",真正"接通硬件、打开中断使能"还需要后续几个步骤。
3. 设备启动:映射寄存器 + 进入 D0 + 打开中断使能
3.1 PLxEvtDevicePrepareHardware ------ 解析资源并映射 BAR
文件:Init.c
典型流程:
-
遍历
ResourcesTranslated,找到如下资源:- BAR0(MMIO 寄存器区):长度
0x200; - BAR2(SRAM 区):长度
PCI9656_SRAM_SIZE; - 可选的 BAR3(另一段 SRAM);
- BAR1(I/O Port,端口访问)。
- BAR0(MMIO 寄存器区):长度
-
使用
LocalMmMapIoSpace映射物理地址到内核虚拟地址:DevExt->RegsBase/DevExt->RegsLength;DevExt->SRAMBase/DevExt->SRAMLength。
-
把寄存器基地址转换为结构体指针:
cDevExt->Regs = (PPCI9656_REGS) DevExt->RegsBase;
映射完成后,可以通过
DevExt->Regs->Int_Csr、DevExt->Regs->Dma0_Csr等寄存器访问中断控制和 DMA 状态。
3.2 PLxEvtDeviceD0Entry ------ 设备进入 D0 工作态
文件:Pci9656.c
流程:
devExt = PLxGetDeviceContext(Device);- 调用
PLxInitWrite(devExt):- 将
DevExt->Regs->Dma0_PCI_DAC寄存器清零; - 将
DevExt->Dma0Csr.uchar清零。
- 将
- 调用
PLxInitRead(devExt):- 将
DevExt->Regs->Dma1_PCI_DAC寄存器清零; - 将
DevExt->Dma1Csr.uchar清零。
- 将
此时设备进入工作态,DMA 通道处于干净状态,但中断线还未真正使能。
3.3 PLxEvtInterruptEnable ------ 真正打开设备中断
文件:IsrDpc.c
该回调在框架调用 IoConnectInterrupt 成功并准备好中断线后被调用:
-
通过
PLxGetDeviceContext(WdfInterruptGetDevice(Interrupt))获取devExt。 -
读取中断控制寄存器 INT_CSR:
cintCSR.ulong = READ_REGISTER_ULONG(&devExt->Regs->Int_Csr); intCSR.bits.PciIntEnable = TRUE; WRITE_REGISTER_ULONG(&devExt->Regs->Int_Csr, intCSR.ulong);
将
PciIntEnable置 1,表示 允许 PLX 芯片向 PCI 总线发中断。至此,中断注册 + 设备寄存器映射 + 中断使能完整闭环。
4. 中断触发:PLxEvtInterruptIsr 执行流程
当 DMA、错误、或其他事件使 PLX 芯片在 Int_Csr 中置位相应标志,且 PciIntEnable=1 时,设备拉高中断线,内核调用:
c
BOOLEAN
PLxEvtInterruptIsr(
IN WDFINTERRUPT Interrupt,
IN ULONG MessageID
)
文件:IsrDpc.c
执行步骤:
-
获取设备扩展:
cdevExt = PLxGetDeviceContext(WdfInterruptGetDevice(Interrupt)); -
读取 INT_CSR:
cintCsr.ulong = READ_REGISTER_ULONG(&devExt->Regs->Int_Csr); -
检查 DMA 通道 0(写 DMA)是否产生中断:
cif (intCsr.bits.DmaChan0IntActive) { devExt->IntCsr.bits.DmaChan0IntActive = TRUE; // 记录软件状态 devExt->Dma0Csr.uchar = READ_REGISTER_UCHAR(&devExt->Regs->Dma0_Csr); devExt->Dma0Csr.bits.Clear = TRUE; // 置 Clear 位 WRITE_REGISTER_UCHAR(&devExt->Regs->Dma0_Csr, devExt->Dma0Csr.uchar); // 写回清中断 isRecognized = TRUE; } -
检查 DMA 通道 1(读 DMA)是否产生中断:
cif (intCsr.bits.DmaChan1IntActive) { devExt->IntCsr.bits.DmaChan1IntActive = TRUE; devExt->Dma1Csr.uchar = READ_REGISTER_UCHAR(&devExt->Regs->Dma1_Csr); devExt->Dma1Csr.bits.Clear = TRUE; WRITE_REGISTER_UCHAR(&devExt->Regs->Dma1_Csr, devExt->Dma1Csr.uchar); isRecognized = TRUE; } -
如识别到本设备中断,且有任意一个 DMA
Done:cif ((isRecognized) && ((devExt->Dma0Csr.bits.Done) || (devExt->Dma1Csr.bits.Done))) { WdfInterruptQueueDpcForIsr(devExt->Interrupt); } -
返回
isRecognized:TRUE表示"这是我的中断";FALSE表示"不是我产生的",框架会尝试让其它共享中断的驱动处理。
ISR 的原则是:短、快、只做必要工作------读取状态、清硬件中断位、做少量软件状态更新,然后排队 DPC 去做重活。
5. DPC 处理:完成 DMA 事务与上层 I/O 请求
ISR 中调用 WdfInterruptQueueDpcForIsr 后,WDF 在合适时机调度:
c
VOID
PLxEvtInterruptDpc(
WDFINTERRUPT Interrupt,
WDFOBJECT Device
)
文件:IsrDpc.c
5.1 进入 DPC 并获取锁
-
获取
devExt:cdevExt = PLxGetDeviceContext(WdfInterruptGetDevice(Interrupt)); -
获取中断自旋锁:
cWdfInterruptAcquireLock(Interrupt);- 保护
devExt->IntCsr、devExt->Dma0Csr、devExt->Dma1Csr等共享状态,防止与新的 ISR 并发修改。
- 保护
5.2 判断哪路 DMA 完成
-
写 DMA 完成判定:
cif ((devExt->IntCsr.bits.DmaChan0IntActive) && (devExt->Dma0Csr.bits.Done)) { devExt->IntCsr.bits.DmaChan0IntActive = FALSE; // 清软件标记 devExt->Dma0Csr.uchar = 0; // 清本地 CSR 副本 writeInterrupt = TRUE; } -
读 DMA 完成判定:
cif ((devExt->IntCsr.bits.DmaChan1IntActive) && (devExt->Dma1Csr.bits.Done)) { devExt->IntCsr.bits.DmaChan1IntActive = FALSE; devExt->Dma0Csr.uchar = 0; readInterrupt = TRUE; } -
释放中断自旋锁:
cWdfInterruptReleaseLock(Interrupt);
5.3 写 DMA 完成路径
c
if (writeInterrupt) {
dmaTransaction = devExt->WriteDmaTransaction;
transactionComplete =
WdfDmaTransactionDmaCompleted(dmaTransaction, &status);
if (transactionComplete) {
PLxWriteRequestComplete(dmaTransaction, status);
}
}
WdfDmaTransactionDmaCompleted:通知 WDF 当前 DMA 操作结束,框架判断该 Transaction 是否全部完成;- 若完成,调用
PLxWriteRequestComplete:- 从
DmaTransaction找到原始WDFREQUEST; - 取得传输字节数;
- 释放
DmaTransaction; - 调用
WdfRequestCompleteWithInformation完成 I/O 请求。
- 从
5.4 读 DMA 完成路径
c
if (readInterrupt) {
dmaTransaction = devExt->ReadDmaTransaction;
length = WdfDmaTransactionGetCurrentDmaTransferLength(dmaTransaction);
dteVA = (PDMA_TRANSFER_ELEMENT) devExt->ReadCommonBufferBase;
while (dteVA->DescPtr.LastElement == FALSE) {
length -= dteVA->TransferSize;
dteVA++;
}
length -= dteVA->TransferSize;
transactionComplete =
WdfDmaTransactionDmaCompletedWithLength(
dmaTransaction,
length,
&status
);
if (transactionComplete) {
PLxReadRequestComplete(dmaTransaction, status);
}
}
- 对读 DMA,使用"Clear-Count 模式"的补数算法,从 DTE 列表中计算实际传输长度;
- 调用
WdfDmaTransactionDmaCompletedWithLength把真实长度告诉 WDF; - 若整个 Transaction 完成,调用
PLxReadRequestComplete,完成原始 Read 请求。
DPC 层是"中断 → DMA 事务 → I/O 请求完成"的核心环节,在较低 IRQL 下安全地做大部分工作。
6. 中断停用与资源回收
6.1 PLxEvtInterruptDisable ------ 关闭设备中断
文件:IsrDpc.c
在设备离开 D0 或驱动卸载前,框架在断开 ISR 之前调用:
c
intCSR.ulong = READ_REGISTER_ULONG(&devExt->Regs->Int_Csr);
intCSR.bits.PciIntEnable = FALSE;
WRITE_REGISTER_ULONG(&devExt->Regs->Int_Csr, intCSR.ulong);
关闭
PciIntEnable,防止在中断处理链拆除过程中还有新中断冲进来。
6.2 PLxEvtDeviceReleaseHardware ------ 释放 BAR 映射
文件:Init.c
- 如果
DevExt->RegsBase非空,调用MmUnmapIoSpace; - 如果
DevExt->SRAMBase非空,同样MmUnmapIoSpace; - 释放对物理资源的映射。
6.3 其他清理回调
PLxEvtDeviceD0Exit:根据目标电源状态决定是否调用PLxShutdown复位硬件;PlxEvtDeviceCleanup:调用PlxCleanupDeviceExtension释放扩展中动态分配的资源;PlxEvtDriverContextCleanup:在驱动卸载时做 WPP 清理等。
7. 总体时序小结
用文字串联完整的中断链路如下:
DriverEntry注册PLxEvtDeviceAdd作为设备添加回调;- 设备枚举时,
PLxEvtDeviceAdd被调用,创建WDFDEVICE和DEVICE_EXTENSION,随后调用PLxInitializeDeviceExtension:- 在其中创建各类 I/O 队列;
- 调用
PLxInterruptCreate创建WDFINTERRUPT; - 初始化 DMA 相关对象;
- PnP 启动流程中,
PLxEvtDevicePrepareHardware映射寄存器和 SRAM; - 设备进入 D0 时,
PLxEvtDeviceD0Entry调用PLxInitWrite和PLxInitRead初始化 DMA 通道寄存器和软件状态; - WDF 在成功连接中断线后调用
PLxEvtInterruptEnable,在 INT_CSR 中置位PciIntEnable,打开设备中断; - 当 DMA 完成或其他事件触发时,PLX 芯片在 INT_CSR 中置位相应位并拉高中断线:
- 内核调用
PLxEvtInterruptIsr; - ISR 读取 INT_CSR,判断 DMA0/1 中断源;
- 通过写 DMA CSR 的
Clear位清除硬件中断状态; - 在
devExt->IntCsr/devExt->Dma0Csr/devExt->Dma1Csr中记录软件状态; - 若发现 DMA
Done,调用WdfInterruptQueueDpcForIsr排队 DPC;
- 内核调用
- DPC 被调度后,
PLxEvtInterruptDpc:- 在
WdfInterruptAcquireLock保护下检查devExt->IntCsr和各 DMA CSR 副本,判断是哪路 DMA 完成; - 释放锁后,对应调用
WdfDmaTransactionDmaCompleted*; - 再调用
PLxWriteRequestComplete或PLxReadRequestComplete完成原始 I/O 请求;
- 在
- 设备停用或驱动卸载时:
PLxEvtInterruptDisable关闭 INT_CSR 中的PciIntEnable;PLxEvtDeviceReleaseHardware解除 MMIO 映射;- 其他清理回调完成剩余资源回收。
通过以上梳理,可以较清晰地看到:
中断注册 → 设备硬件映射 → D0 初始化 → 中断使能 → ISR 快速处理中断 → DPC 完成 DMA 与请求 → 中断停用与资源释放
整个链路严格遵循 WDF 的推荐模式,并结合 PLX9656 的 DMA / 中断寄存器实现了完整的中断驱动流程。