PLX9x5x_PCI_Driver 中断从注册到调用完整流程

在编写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

主要步骤:

  1. 填充即将使用到的 PnP / 电源管理回调:

    • EvtDevicePrepareHardware = PLxEvtDevicePrepareHardware
    • EvtDeviceReleaseHardware = PLxEvtDeviceReleaseHardware
    • EvtDeviceD0Entry = PLxEvtDeviceD0Entry
    • EvtDeviceD0Exit = PLxEvtDeviceD0Exit
  2. 设置设备级同步范围:

    • attributes.SynchronizationScope = WdfSynchronizationScopeDevice
    • 含义:与该 WDFDEVICE 直接关联的对象的回调(队列回调、PLxEvtInterruptDpc 等)会被框架用一个内部"设备锁"串行化,减少显式自旋锁的需求。
  3. 调用 WdfDeviceCreate(&DeviceInit, &attributes, &device) 创建 WDFDEVICE

  4. 获取并初始化 DEVICE_EXTENSION

    • 通过 PLxGetDeviceContext(device) 得到 devExt
    • 保存 devExt->Device = device
  5. 为该设备创建接口:

    • 通过 WdfDeviceCreateDeviceInterface(device, &GUID_PLX_INTERFACE, NULL),给用户态应用提供符号链接接口。
  6. 设置电源管理策略:

    • PLxSetIdleAndWakeSettings(devExt)
  7. 设备扩展初始化(非常关键)

    • 调用 PLxInitializeDeviceExtension(devExt)
      • 创建读写/控制队列;
      • 创建 DMA 相关对象;
      • 创建中断对象 WDFINTERRUPT(中断"注册"的核心)

2. 设备扩展初始化:创建 WDFINTERRUPT(中断"注册")

2.1 PLxInitializeDeviceExtension

文件:Init.c

该函数在 PLxEvtDeviceAdd 中被调用,主要职责:

  1. 设置 MaximumTransferLength,计算 DMA 需要的 DMA_TRANSFER_ELEMENT 数量。

  2. 创建三个 I/O 队列:

    • WriteQueue:顺序队列,绑定 PLxEvtIoWrite
    • ReadQueue:顺序队列,绑定 PLxEvtIoRead
    • ControlQueue:顺序队列,绑定 PLxEvtIoDeviceControl
  3. 为不同的 WdfRequestType 配置请求分发到对应队列。

  4. 创建中断对象

    c 复制代码
    status = PLxInterruptCreate(DevExt);
  5. 初始化 DMA:

    c 复制代码
    status = PLxInitializeDMA(DevExt);

2.2 PLxInterruptCreate ------ 配置并创建 WDFINTERRUPT

文件:IsrDpc.c

核心逻辑:

  1. 初始化 WDF_INTERRUPT_CONFIG

    c 复制代码
    WDF_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 做序列化处理,简化同步逻辑。
  2. 调用 WdfInterruptCreate

    c 复制代码
    status = 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

典型流程:

  1. 遍历 ResourcesTranslated,找到如下资源:

    • BAR0(MMIO 寄存器区):长度 0x200
    • BAR2(SRAM 区):长度 PCI9656_SRAM_SIZE
    • 可选的 BAR3(另一段 SRAM);
    • BAR1(I/O Port,端口访问)。
  2. 使用 LocalMmMapIoSpace 映射物理地址到内核虚拟地址:

    • DevExt->RegsBase / DevExt->RegsLength
    • DevExt->SRAMBase / DevExt->SRAMLength
  3. 把寄存器基地址转换为结构体指针:

    c 复制代码
    DevExt->Regs = (PPCI9656_REGS) DevExt->RegsBase;

映射完成后,可以通过 DevExt->Regs->Int_CsrDevExt->Regs->Dma0_Csr 等寄存器访问中断控制和 DMA 状态。

3.2 PLxEvtDeviceD0Entry ------ 设备进入 D0 工作态

文件:Pci9656.c

流程:

  1. devExt = PLxGetDeviceContext(Device);
  2. 调用 PLxInitWrite(devExt)
    • DevExt->Regs->Dma0_PCI_DAC 寄存器清零;
    • DevExt->Dma0Csr.uchar 清零。
  3. 调用 PLxInitRead(devExt)
    • DevExt->Regs->Dma1_PCI_DAC 寄存器清零;
    • DevExt->Dma1Csr.uchar 清零。

此时设备进入工作态,DMA 通道处于干净状态,但中断线还未真正使能。

3.3 PLxEvtInterruptEnable ------ 真正打开设备中断

文件:IsrDpc.c

该回调在框架调用 IoConnectInterrupt 成功并准备好中断线后被调用:

  1. 通过 PLxGetDeviceContext(WdfInterruptGetDevice(Interrupt)) 获取 devExt

  2. 读取中断控制寄存器 INT_CSR:

    c 复制代码
    intCSR.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

执行步骤:

  1. 获取设备扩展:

    c 复制代码
    devExt = PLxGetDeviceContext(WdfInterruptGetDevice(Interrupt));
  2. 读取 INT_CSR:

    c 复制代码
    intCsr.ulong = READ_REGISTER_ULONG(&devExt->Regs->Int_Csr);
  3. 检查 DMA 通道 0(写 DMA)是否产生中断:

    c 复制代码
    if (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;
    }
  4. 检查 DMA 通道 1(读 DMA)是否产生中断:

    c 复制代码
    if (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;
    }
  5. 如识别到本设备中断,且有任意一个 DMA Done

    c 复制代码
    if ((isRecognized) &&
        ((devExt->Dma0Csr.bits.Done) ||
         (devExt->Dma1Csr.bits.Done))) {
        WdfInterruptQueueDpcForIsr(devExt->Interrupt);
    }
  6. 返回 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 并获取锁

  1. 获取 devExt

    c 复制代码
    devExt = PLxGetDeviceContext(WdfInterruptGetDevice(Interrupt));
  2. 获取中断自旋锁:

    c 复制代码
    WdfInterruptAcquireLock(Interrupt);
    • 保护 devExt->IntCsrdevExt->Dma0CsrdevExt->Dma1Csr 等共享状态,防止与新的 ISR 并发修改。

5.2 判断哪路 DMA 完成

  1. 写 DMA 完成判定:

    c 复制代码
    if ((devExt->IntCsr.bits.DmaChan0IntActive) &&
        (devExt->Dma0Csr.bits.Done)) {
    
        devExt->IntCsr.bits.DmaChan0IntActive = FALSE;  // 清软件标记
        devExt->Dma0Csr.uchar = 0;                      // 清本地 CSR 副本
    
        writeInterrupt = TRUE;
    }
  2. 读 DMA 完成判定:

    c 复制代码
    if ((devExt->IntCsr.bits.DmaChan1IntActive) &&
        (devExt->Dma1Csr.bits.Done)) {
    
        devExt->IntCsr.bits.DmaChan1IntActive = FALSE;
        devExt->Dma0Csr.uchar = 0;
    
        readInterrupt = TRUE;
    }
  3. 释放中断自旋锁:

    c 复制代码
    WdfInterruptReleaseLock(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. 总体时序小结

用文字串联完整的中断链路如下:

  1. DriverEntry 注册 PLxEvtDeviceAdd 作为设备添加回调;
  2. 设备枚举时,PLxEvtDeviceAdd 被调用,创建 WDFDEVICEDEVICE_EXTENSION,随后调用 PLxInitializeDeviceExtension
    • 在其中创建各类 I/O 队列;
    • 调用 PLxInterruptCreate 创建 WDFINTERRUPT
    • 初始化 DMA 相关对象;
  3. PnP 启动流程中,PLxEvtDevicePrepareHardware 映射寄存器和 SRAM;
  4. 设备进入 D0 时,PLxEvtDeviceD0Entry 调用 PLxInitWritePLxInitRead 初始化 DMA 通道寄存器和软件状态;
  5. WDF 在成功连接中断线后调用 PLxEvtInterruptEnable,在 INT_CSR 中置位 PciIntEnable,打开设备中断;
  6. 当 DMA 完成或其他事件触发时,PLX 芯片在 INT_CSR 中置位相应位并拉高中断线:
    • 内核调用 PLxEvtInterruptIsr
    • ISR 读取 INT_CSR,判断 DMA0/1 中断源;
    • 通过写 DMA CSR 的 Clear 位清除硬件中断状态;
    • devExt->IntCsr / devExt->Dma0Csr / devExt->Dma1Csr 中记录软件状态;
    • 若发现 DMA Done,调用 WdfInterruptQueueDpcForIsr 排队 DPC;
  7. DPC 被调度后,PLxEvtInterruptDpc
    • WdfInterruptAcquireLock 保护下检查 devExt->IntCsr 和各 DMA CSR 副本,判断是哪路 DMA 完成;
    • 释放锁后,对应调用 WdfDmaTransactionDmaCompleted*
    • 再调用 PLxWriteRequestCompletePLxReadRequestComplete 完成原始 I/O 请求;
  8. 设备停用或驱动卸载时:
    • PLxEvtInterruptDisable 关闭 INT_CSR 中的 PciIntEnable
    • PLxEvtDeviceReleaseHardware 解除 MMIO 映射;
    • 其他清理回调完成剩余资源回收。

通过以上梳理,可以较清晰地看到:

中断注册 → 设备硬件映射 → D0 初始化 → 中断使能 → ISR 快速处理中断 → DPC 完成 DMA 与请求 → 中断停用与资源释放

整个链路严格遵循 WDF 的推荐模式,并结合 PLX9656 的 DMA / 中断寄存器实现了完整的中断驱动流程。

相关推荐
自由的好好干活11 小时前
PLX 9x5x PCI 驱动程序执行流程详解(Qoder生成)
驱动开发·ai编程
github.com/starRTC14 小时前
Claude Code中英文系列教程:创建自己的斜杠快捷命令
ai编程
顾北1215 小时前
阿里百炼AI大模型接入指南
阿里云·ai编程
viqjeee15 小时前
ALSA驱动开发流程
数据结构·驱动开发·b树
IT 行者17 小时前
Claude之父AI编程技巧十:安全最佳实践——安全与效率的平衡艺术
安全·ai编程
砚边数影18 小时前
AI环境搭建(一):JDK17 + Maven 配置,Java开发环境标准化流程
数据库·人工智能·ai·ai编程
光影少年18 小时前
前端如何调用gpu渲染,提升gpu渲染
前端·aigc·web·ai编程
github.com/starRTC20 小时前
Claude Code中英文系列教程:在同一个项目里面并行使用多个Claude Code
ai编程
砚边数影21 小时前
Java基础强化(三):多线程并发 —— AI 数据批量读取性能优化
java·数据库·人工智能·ai·性能优化·ai编程