介绍PCIe的中断机制

背景介绍

PCIe的spec从硬件的角度定义了各个层,以及复杂的交互。但如果只是站在软件开发者的角度,PCIe协议包含几个主要的数据方向:

  1. RC->EP的读和写。

  2. EP->RC的读和写。

  3. EP->RC的中断,设备端用于通知主机端。

  

 

 

这里只有单向的从EP到RC的中断,并没有明确定义如何实现相反方向的通知。从需求的层面,设备端需要尽量的多样化,而主机端则需要尽量的通用。PCIe bus 是树形结构,多个EP都可以向RC发送中断,RC需要进行区分。而且,RC的driver通常由中断数量的限制。所以为了保持兼容性,需要在spec中专门定义,如何由设备侧给主机侧发送通知。


主机端到设备端的通知,取决于设备侧soc的设计,比如使用DMA copy的时候,host在准备好数据后,更新DMA ring的index,然后由设备侧的向它的CPU发送中断。


下面这幅图总结了PCI和PCIe的中断,按照类型可以分为GPIO中断和message中断,按照用途可以分为PM控制中断(PERST/WAKE/CLKREQ)和数据通信中断(INT#,MSI,MSI-X)。本文主要介绍数据通信类型的中断。






  1. GPIO中断:这类中断是通过General Purpose Input/Output(GPIO)引脚实现的。它们用于设备之间的基本信号传递,例如电源状态变化等。GPIO中断通常是通过设备的中断控制器引脚连接到主机的中断控制器来实现的。

  2. Message中断:这类中断是通过消息传递机制实现的。它们用于设备之间的高级通信,例如设备状态变化、数据传输完成等。PCIe中的Message中断可以分为MSI和MSI-X中断。这是一种现代的中断机制,使用memory write消息,设备向主机发送更多的信息,例如中断向量、中断优先级等。

INT#中断



INT#中断是一种旧的PCI协议的传统的中断机制,用于设备端向主机端发送通知。在PCIe中,每个设备都有一个或多个INT#引脚(INTA、INTB、INTC、INTD),可以通过触发INT#信号来通知主机发生了某个事件。INT#中断是基于电平触发的,即设备端将INT#引脚拉低或拉高以触发中断。



一个简单的PCI总线INTx中断实现流程,如下图所示。

1. 首先,PCI设备通过INTx边带信号产生中断请求,经过中断控制器(Interrupt Controller,PIC)后,转换为INTR信号,并直接发送至CPU;

2. CPU收到INTR信号置位后,意识到了中断请求的发生,但是此时并不知道是什么中断请求。于是通过一个特殊的指令来查询中断请求信息,该过程一般被称为中断应答(Interrupt Acknowledge);

3. 该特殊指令被发送至PIC后,PIC会返回一个8bits的中断向量(Interrupt Vector)值给CPU。该中断向量值与其发送的INTR请求是对应的;

4. CPU收到来自PIC的中断向量值后,会去其Memory中的中断向量表(Interrupt Table)中查找对应的中断服务程序(Interrupt Service Routines,ISR)在Memory的位置;

5. 然后CPU读取ISR程序,进而处理该中断。



image.png


上面的例子主要是基于早期的单核CPU设计的,并没有考虑到目前多核CPU的情况。因此,在后续的PCI Spec中,将PIC替换为IO APIC(Advanced Programmed Interrupt Controller)。

实际上,在PCIe总线中,传统的中断机制(INTx)已经很少被使用,很多应用甚至直接将该功能禁止了。无论是在PCI总线(V2.3及以后的版本),还是PCIe总线中,都可以通过配置空间中的配置命令寄存器(Configuration Command Register)来禁止INTx中断机制。一旦使能了MSI/MSI-X,PCI总线/PCIe总线便会自动地禁止INTx。


PCIe总线继承了PCI总线的INTx中断机制,但是在实际的PCIe设备之间的中断信息传输中使用的并非边带信号INTx,而是基于消息(Message)的。其中Assert_INTx消息表示INTx信号的下降沿。Dessert_INTx消息表示INTx信号的上升沿。当发送这两种消息时,PCIe设备还会将配置空间的相关中断状态bit的值更新。


MSI中断


由于设备侧只有一个中断到主机侧,如果设备有多种中断需要提供,则需要主机在处理中断的时候,查询设备的寄存器地址,进一步了解中断的详细信息,这种方式比较低效和缺乏可扩展性。


MSI config


前面的文章中介绍过,MSI本质上是一种Memory Write,和PCIe总线中的Message概念半毛钱关系都没有。并且,MSI的Data Payload也是固定的,始终为1DW。

由于MSI也是从PCI总线继承而来的,因此MSI相关的寄存器也存在于配置空间中的PCI兼容部分(前256个字节)。如下图所示,MSI有四种类型:
image.png
其中Capability ID的值是只读的,05h表示支持MSI功能。

Next Capability Pointer也是只读的,其用于查找下一个Capability Structure的位置,其值为00h则表示到达Linked List的最后了。

Message Control Register用于确定MSI的格式与支持的功能等信息,如下图所示:
image.png
具体描述如下:
image.png
Message Address Register:32-bit最低两位固定为0,使得该地址是DW对齐的。

当Mask Bits将相关的中断向量(Interrupt Vector)屏蔽后,该MSI将不会被发送。软件可以通过这种方式来使能或者禁止某些MSI的发送。

如果相关中断向量没有被屏蔽,则如果发生了相关中断请求,这时Pending Bits中的相应bit则会被置位。一旦中断信息被发出,则该bit会立即被清零。

注:可能有的人会有疑惑了(无论是Mindshare的书,还是PCI的Spec都没有明确解释),因为Mask Bits和Pending Bits都只有32位,而8位的中断向量号最多可以表示256个!显然,32位最多只能对应32个中断向量号,无法支持256个的。

要解答这个问题,其实MSI Data中的8个bit中,只有最低的几个bit是表示中断vector,高位的bit则用于表示MSI的一些公用的特性。表示vector的bit位个数,取决于在配置空间中声明EP支持的vector个数,比如如果声明为支持32个vector,则最低5个bit表示vector,最高的3个bit位RC可以在其中填入一些自定义的属性,EP在构造MSI packet的时候,将这些bit送给RC侧,RC侧的中断控制器可能会基于这些信息做一些特殊的区分。但是这个功能,似乎并没有真正的使用起来。

MSI message

PCIe设备会根据配置空间中的MSI请求信息,来创建Memory Write TLP,来将MSI信息发送出去。作为一种特殊的TLP,传递MSI的TLP需要遵循以下规则:

  • No Snoop和Relaxed Ordering bits的值必须为0

  • TLP长度值必须为01h

  • First BE必须为1111b

  • Last BE必须为0000b

  • 地址是直接从配置空间中的响应位置复制过来的

如下图所示:
image.png

参考代码


MSI packet并不是有SW构造出来的,在PCIe IP参考代码中,提供了向PARF寄存器写寄存器,由硬件自动构造MSI message。


mhi_link_triggerinterrupt(vec)

pcie_core_sendparfmsi(vec)


MSI-X 中断

需要MSI-X的原因


PCIe总线自3.0版本开始支持MSI-X机制,对MSI做出了一些升级和改进,以克服MSI机制的三个主要的缺陷:

  1. 随着系统的发展,对于特定的大型应用,32个中断向量不够用了;

  2. 只有一个目标地址使得多核CPU情况下的,静态中断分配变得困难。如果能够使每个向量对应不同的唯一的地址,便会灵活很多;

  3. 某些应用中的中断优先级混乱问题。

MSI-X config

有趣的是,MSI只支持32个中断向量,而MSI-X支持多达2048个中断向量,但是MSI-X的相关寄存器在配置空间中占用的空间却更小。这是因为中断向量信息并不直接存储在这里,而是在一款特殊的Memory(MIMO)中。并通过BIR(Base address Indicator Register, or BAR Index Register)来确定其在MIMO中的具体位置。如下图所示:
image.png
Message Control寄存器的具体描述如下:
image.png
MSI-X查找表的示意图如下:
image.png
结构图如下:
image.png
类似的,Pending Bits则位于另一个Memory中,其结构图如下:
image.png

从上面的描述可以看到,MSI-X的设备端需要提供两个额外的BAR,host将它们映射到主机地址中。RC向MSI-X table中写入至多2048行vector配置,每一行包含以下信息:

  1. 2 word 的地址,通常它们是Host中断控制器的基地址,

  2. 1word的DATA,用于指定中断message的payload信息,应该就是vector number,

  3. 1word的控制信息。(MSI中,控制信息只有3bit)

 

另外一个BAR地址用于将pending interrupt,主机驱动代码读取这一段区域,可以知道pending的中断列表。


MSI-X 消息产生流程


MSI-X中断消息产生流程比MSI更复杂一些,它大致有以下步骤。相比于发送MSI时,EP HW可以直接使用配置空间中指明的MSI基地址,MSI-X需要先去查询MSI-X table才能够获取到中断对应的address和data信息。


  1. RC driver映射MSI-X 和pending bit table到其地址空间。

  2. RC driver向MSI-X table中填入它所支持的中断向量配置表。

  3. EP driver向EP HW写需要触发的中断号

  4. EP HW查询MSI-X table,从其中取该中断对应的64bit地址信息。

  5. EP HW查询MSI-X table,从期中取该中断的DATA和Control信息。

  6. EP HW构造MSI-X消息,填入上面两个步骤中得到的地址和DATA信息。

  7. EP HW发送该TLP到RC侧。

  8. RC HW从TLP中取address和data信息,将它们传到GIC。

  9. GIC HW触发中断给CPU

  10. CPU开始处理中断。

 

由于PCIe设备支持MSI和MSI-X方式,虽然不是同时支持,但仍然给硬件设计带来挑战,如何将MSI-X的上述流程,暴露给其它中断触发模块(软件或者硬件都有可能)的接口应该保持一致。在发送MSI的时候,需要提供base address和中断向量号,那么在设计MSI-X的时候,可以由SOC这边提供一个虚构的地址,配置给MSI-X触发模块中。当这些模块使用这个虚构地址产生写操作的时候,这个transaction被送到PCIe的EP HW中,EP HW会比较所有的MEMw操作的目的地址和MSI-X虚拟地址,如果相同,则是一个MSI-X的中断。在中断触发模块这里,接口保持不变,将MSI和MSI-X的差异隐藏在PCIe EP HW内部。

参考代码


mhi_link_trigger_msix



参考资料:

  1. https://github.com/apachecn/apachecn-linux-zh/blob/master/docs/master-linux-device-driver-dev/11.md

  2. PCIe扫盲——中断机制介绍(INTx )https://blog.csdn.net/justlxy/article/details/116881310 

PCIe专栏 https://blog.csdn.net/justlxy/category_10956312.html

posted @   jincheng23  阅读(2182)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示