PCIe EP概述—工程角度
本文聚焦PCIe端点设备(EP)的功能定位与运行机制。首先回顾PCIe总线特性,比较其与PCI-X的关键差异。重点解析EP作为终端设备的核心特征:1)配置空间管理,包含设备识别、资源分配等功能;2)传输分层架构;3)中断处理机制(INTx/MSI/MSI-X);4)地址转换单元(ATU)实现跨域访问。文章详细阐述了EP驱动开发流程,包括设备枚举、资源初始化、中断配置和DMA通信实现,并对比了Blo
本文的目标是聚焦于EP,了解EP的功能定位与基础的运行机制,侧重于固件应用角度,而不是阐述整体PCIe协议。
PCIe基础概念回顾
PCIe是一种高速的串行计算机扩展总线标准,用于连接计算机主板上的高性能硬件设备。它是现代计算机中最重要的内部接口,取代了老旧的 PCI和 PCI-X 总线。
如下图所示为PCI、PCI-X与PCIe1.0协议的发布日期。

相关总线协议与对应发布日期
PCI-X与PCIe总线存在诸多差异,我们可以挑选如下几个典型的特征维度进行对比分析,如总线类型、频率与带宽等。
PCI-X与PCIe总线关键特性差异
|
特性 |
PCI-X |
PCI Express |
|---|---|---|
|
总线类型 |
并行总线 |
串行总线 |
|
工作方式 |
共享总线。所有设备挂在同一条总线上,分时共享带宽。 |
点对点 交换结构。每个设备独享与芯片组的通道,互不干扰。 |
|
时钟频率 |
最高133 MHz (PCI-X 2.0 可达 533MHz,但极少见) |
没有共享时钟,使用内嵌时钟的差分信号。 |
|
带宽 |
可扩展性差 |
可扩展性强。通过增加通道数(Lanes)来倍增带宽。 |
再以带宽为例,下图展示PCIe协议带宽随着版本更新的变化。
GT/s与Gbps单位的区别:
GT/s:关注物理层时钟频率和信号跳变次数,与编码开销无关;
Gbps:表示有效数据比特率(含协议开销),需扣除编码冗余和链路层控制信息。

PCIe technology is flexible to meet bandwidth needs from handheld/client to server/HPC

PCIe I/O bandwidth doubles every 3 years
PCIe 拓扑结构中的关键角色
PCIe 拓扑结构中的关键角色如下如所示:

PCIe 拓扑结构中的关键角色
其中:
RC(Root Complex ,根复合体):系统的核心和管理器;
Switch (交换器):扩展连接的核心组件;
Endpoint (端点设备):本次介绍的核心——功能的最终实现者;
Bridge (桥接设备):与其它总线协议的转换器;
|
特性 |
RC |
Switch |
EP |
|---|---|---|---|
|
在拓扑中的位置 |
根节点 |
分支 |
叶节点 |
|
主要功能 |
连接CPU/内存,发起和管理事务 |
扩展端口,路由数据包 |
实现特定硬件功能 |
|
能否发起请求 |
是 |
否 (仅转发) |
是 (如DMA请求) |
|
否连接下级设备 |
是 (直接连接EP或Switch) |
是 (下游端口连接EP或Switch) |
否 (是终点) |
|
对软件是否透明 |
否 (是枚举过程的起点) |
是 (操作系统看不到它) |
否 |
功能用途
PCIe EP(Endpoint)是PCI Express(PCIe)总线架构中的终端设备,位于PCIe拓扑结构的末端,负责执行具体功能并响应来自上游设备(如Root Port)的指令。
定义:在 PCIe 拓扑中发起或完成事务请求的终端设备
|
EP类型 |
应用场景 |
性能特性 |
|
存储控制器 |
NVMe SSD |
高吞吐量(PCIe 5.0 x4可达14 GB/s) |
|
图形处理器 |
GPU |
需大带宽(PCIe 6.0 x16带宽达256 GB/s) |
核心特征:作为请求者(Requester)或完成者(Completer)

添加图片注释,不超过 140 字(可选)
与其它设备类型的本质区别:
EP 是数据终点(不转发其他设备数据),Switch 是纯数据通路(无本地数据处理),RC 是总线控制核心(连接CPU与PCIe域)。
EP关键功能与机制
PCIe EP主要的功能模块/或者概念为,配置空间管理、传输分层、中断处理机制、地址转换等。
配置空间
配置空间是每个PCIe设备(包括EP、RC和Switch)必须提供的标准化寄存器区域:

PCIe配置空间布局
配置空间的寄存器主要作用为以下四种。
-
设备识别:包含厂商ID(Vendor ID)、设备ID(Device ID) 等,让系统知道“这是什么设备”;
-
资源分配:包含基址寄存器(BARs),系统通过向BAR写入地址来为该设备分配内存或I/O空间,告诉设备“你的资源在哪里”;
-
功能控制:包含命令寄存器(Command Register),用于启用或禁用设备的中断、内存访问等功能;
-
能力扩展:包含一个能力链表(Capabilities List),列出了设备支持的高级特性(如MSI/MSI-X中断、电源管理等)。
以Class Code为例,对比分析该寄存器的取值。
下图为PCIe SPEC规定的Class Code。

添加图片注释,不超过 140 字(可选)

PCIe SPEC规定的Class Code
下图为PCIe SPEC规定的Class Code 0x03H的Subclass Code。

PCIe SPEC规定的Class Code 0x03H的Subclass Code
通过对比可以看到,Func0 EP的Class Code的取值符合预期,为VGA类型。

Windows PC VGA
其它寄存器类似,不再一一展开进行对比分析。
RC侧读写EP配置空间方法
C语言
通过下述函数可以在内核驱动中直接读写配置空间寄存器,需在设备启用后调用。
pci_read_config_byte(struct pci_dev *dev, int where, u8 *val) pci_read_config_word(struct pci_dev *dev, int where, u16 *val) pci_read_config_dword(struct pci_dev *dev, int where, u32 *val) pci_write_config_byte(struct pci_dev *dev, int where, u8 val) pci_write_config_word(struct pci_dev *dev, int where, u16 val) pci_write_config_dword(struct pci_dev *dev, int where, u32 val)
struct pci_dev 是 Linux 内核中用于描述 PCI/PCIe 设备的核心数据结构,表示系统中的一个 PCI/PCIe 物理设备(如网卡、显卡)。
Shell
lspci和setpci是Linux系统中用于管理 PCI/PCIe设备的底层工具,属于pciutils工具集的核心组件。
|
工具 |
主要功能 |
用户层级 |
典型场景 |
|---|---|---|---|
|
lspci |
信息查询 |
普通用户/管理员 |
硬件识别、状态监控、故障排查 |
|
setpci |
寄存器级配置 |
高级用户/开发者 |
底层调试等 |
setpci示例:
# 读取设备的 Vendor ID(偏移地址 0x00) setpci -s 00:02.0 0x00.L # 修改设备配置寄存器(需谨慎操作) setpci -s 03:00.0 0x8.L=0x1000
PCIe Vendor ID查询链接: https://pcisig.com/membership/member-companies

PC RC用setpci查看厂商信息

PCIe Vendor ID 0x8086
传输分层

传输分层示意图
中断机制
|
特性 |
INTx |
MSI |
MSI-X |
|---|---|---|---|
|
中断传递方式 |
虚拟引脚消息 |
Memory Write TLP |
Memory Write TLP |
|
最大向量数 |
4 |
32 |
2048 |
|
配置空间 |
Interrupt Pin 寄存器 |
MSI Capability 结构 |
MSI-X Capability 结构 |
|
地址灵活性 |
固定路由 |
全局统一地址 |
每个向量独立地址 |
|
典型应用 |
兼容传统设备/UEFI 启动 |
通用高性能设备 |
多队列/低延迟设备 |

PCIe与Legacy PCI的中断传递路径
相比于INTx使用MSI至少存在两个优势:
-
根据定义,MSI是一个排他性的中断向量。这意味着中断处理程序不需要验证其设备是 否引起了中断;
-
MSI避免了DMA/IRQ竞争条件。到主机内存的DMA被保证在MSI交付时对主机CPU是可 见的。这对数据一致性和避免控制数据过期都很重要。这个保证允许驱动程序省略MMIO读取,以刷新DMA流。
MSI与MSI-X区别:
MSI和MSI-X的根本区别在于如何分配多个“向量”。MSI需要连续的向量块,而 MSI-X可以分配几个单独的向量。
中断路由管理的硬件/软件协作
|
组件 |
功能 |
|---|---|
|
EP |
根据配置发起INTx消息或MSI写入;更新中断状态寄存器。 |
|
Switch |
转发INTx消息;透传MSI TLP(不修改内容)。 |
|
RC |
转换INTx为硬件中断;处理MSI写入至中断控制器;管理中断映射表。 |
|
驱动/OS |
配置MSI地址/向量;注册ISR;处理中断共享(INTx)或虚拟化中断重定向。 |
地址转换单元ATU
PCIe总线域地址空间是PCIe设备可直接访问的地址范围,与CPU的地址空间分离,二者通过地址转换单元(ATU)进行跨域访问。
跨域访问分为两种:Outbound与Inbound。

ATU示例
Outbound ATU:是存储器域访问PCI总线,将CPU发出的存储器域地址转换为PCIe域地址(如CPU写设备寄存器);
Inbound ATU:是PCI总线访问存储域,将PCIe设备发起的访问(如DMA读写内存)从PCIe域转换为存储器域地址。
BAR空间
BAR(Base Address Register)空间是用于定义设备寄存器或缓冲区在系统地址空间中的位置和属性。BAR空间将设备内部资源(如控制寄存器、数据缓冲区)映射到系统地址空间,使CPU或DMA控制器可通过内存访问与EP设备通信。
EP侧:

EP Bar寄存器偏移

PCIe Spec Bar寄存器
所有基址寄存器的位0均为只读,用于确定该寄存器是映射到内存空间还是I/O空间。
值得注意的是,PCI设备的BAR0~5寄存器和PCI桥的Base寄存器保存的地址都是PCI总线地址。
RC侧:
PCI桥的Base、Limit寄存器保存该桥所管理的PCI子树的存期或者I/O空间的及地址和长度。

BAR寄存器初始化

Type 1 Configuration Space Header
配置映射地址的方法:

RC设备树配置的映射地址
属性位: npt000ss bbbbbbbb dddddfff rrrrrrrr n: relocatable region flag (doesn't play a role here) p: prefetchable (cacheable) region flag t: aliased address flag (doesn't play a role here) ss: space code 00: configuration space 01: I/O space 10: 32 bit memory space 11: 64 bit memory space bbbbbbbb: The PCI bus number. PCI may be structured hierarchically. So we may have PCI/PCI bridges which will define sub busses. ddddd: The device number, typically associated with IDSEL signal connections. fff: The function number. Used for multifunction PCI devices. rrrrrrrr: Register number; used for configuration cycles.
ATU
ATU负责管理与EP间内存地址、配置空间及消息的路由转换,其核心作用是解决不同地址域的映射问题。
典型的ATU寄存器组包括:
基地址寄存器:定义要转换的源地址范围的起始地址。
限制寄存器:定义源地址范围的结束地址。
目标地址寄存器:定义转换后的目标地址。
控制寄存器:启用/禁用该ATU单元。
一个系统通常包含多个ATU单元,以同时处理多个不同的地址窗口和访问类型。
DMA
PCIe RC与EP中的DMA通信是实现高性能数据传输的核心机制,依赖于前文介绍的地址转换与中断
RC 发起 DMA(RC → EP 内存) RC 主动读写 EP 的 BAR 空间(如寄存器或缓冲区),通过 Memory Write TLP 发送数据。
|
特性 |
EP 端实现 |
RC 端实现 |
|---|---|---|
|
主动发起方 |
EP 可主动读写主机内存 |
RC 主动读写 EP 的 BAR 空间 |
|
地址映射方向 |
EP 物理地址 → RC 总线地址(Outbound) |
EP BAR → RC 虚拟地址(Inbound) |
|
驱动复杂度 |
需处理 BAR 初始化、中断配置 |
需管理总线枚举、IOMMU 配置 |
|
典型场景 |
数据采集卡(FPGA 主动上传) |
主机控制外部设备(如 GPU 配置) |
代码分析
宏观逻辑
PCIe EP驱动的核心目的,是让主机RC侧能够识别、配置、并与之通信的软件接口,作为主机操作系统和PCIe硬件设备之间的桥梁。
比如RC的EP驱动pci_test_driver.c文件的整体逻辑如下图所示:

RC侧EP驱动的整体逻辑
当驱动退出时,它只是调用 pci_unregister_driver() ,PCI层会自动调用驱动处理 的所有设备的移除钩子。
资源初始化

资源初始化流程图
int pci_register_driver(struct pci_driver *driver)
pci_register_driver()函数是 Linux 内核中用于注册 PCI/PCIe 设备驱动的核心接口函数,其作用是将驱动程序绑定到 PCI 总线,实现设备的自动探测与初始化。
|
字段 |
作用 |
示例/说明 |
|---|---|---|
|
name |
驱动名称(字符串标识) |
"igb"(Intel 网卡驱动) |
|
id_table |
支持的设备 ID 列表(struct pci_device_id 数组) |
包含厂商 ID、设备 ID 等匹配信息 |
|
probe |
设备初始化函数(资源分配、寄存器配置等) |
调用 pci_enable_device()、request_irq() 等 |
|
remove |
设备卸载函数(释放资源、禁用中断等) |
调用 free_irq()、pci_disable_device() |
通过传入 struct pci_driver 结构体,驱动向 PCI 子系统注册自身,声明支持的设备列表(通过 id_table)及操作回调函数(如 probe、remove)、
PCI 总线在枚举设备时,会调用总线的 match() 函数,将设备的 vendor/device ID 与驱动的 id_table 匹配,匹配成功则触发驱动的 probe() 函数。
pci_get_device
struct pci_dev * pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev *from)
pci_get_device() 函数是 Linux 内核中用于遍历 PCI 设备的接口之一,它通过供应商 ID(Vendor ID)和设备 ID(Device ID)匹配并返回一个 struct pci_dev 结构体的指针。该函数主要用于设备初始化、驱动绑定或动态设备发现场景。通过返回的 pci_dev 指针,可访问其成员字段获取硬件和配置信息。
唤醒处于暂停状态的设备。
分配设备的I/O和内存区域(如果BIOS没有这样做)。
分配一个IRQ(如果BIOS没有)
|
分类 |
字段成员 |
作用 |
|---|---|---|
|
设备标识 |
vendor, device |
设备型号的唯一标识符 |
|
subsystem_vendor, subsystem_device |
区分定制版本设备 |
|
|
设备类与功能 |
class |
设备的功能类别 |
|
revision |
硬件修订版本 |
|
|
总线拓扑 |
bus |
所属总线及拓扑位置 |
|
slot |
物理插槽信息 |
|
|
资源与配置空间 |
resource[] |
描述BAR区域资源 |
|
irq |
中断号 |
|
|
驱动管理状态 |
driver |
关联的PCI驱动程序 |
|
dev |
内核设备模型的关联结构 |
pci_enable_device
int pci_enable_device(struct pci_dev *dev);
PCI设备驱动初始化的关键步骤之一,用于激活PCI设备并使其可被操作系统访问。其核心职责是激活设备硬件并分配必要资源。
pci_set_master
int pci_set_master(struct pci_dev *dev);
用于启用设备的 DMA(Direct Memory Access)能力。其核心作用是通过设置 PCI 配置空间中控制寄存器的总线主控位(Bus Master Enable bit),允许设备主动发起 DMA 传输(例如直接读写主机内存)。
中断初始化
INTx
devm_request_irq()函数注册中断回调,中断号通过pci_get_device()函数获取。
MSI

MSI中断初始化流程
pci_msi_vec_count
int pci_msi_vec_count(struct pci_dev *dev);
Linux 内核中用于查询 PCI 设备支持的 MSI(Message Signaled Interrupt)或 MSI-X 中断向量数量的函数。该函数通过读取设备的 PCI 配置空间实现。
pci_alloc_irq_vectors
int pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs,unsigned int max_vecs, unsigned int flags);
这将导致PCI支持将CPU向量数 据编程到PCI设备功能寄存器中。许多架构、芯片组或BIOS不支持MSI或MSI-X,调用 pci_alloc_irq_vectors 时只使用PCI_IRQ_MSI和PCI_IRQ_MSIX标志会失败, 所以尽量也要指定 PCI_IRQ_INTX 。
Linux 内核中用于为 PCI/PCIe 设备分配中断向量的核心函数,支持 MSI和 MSI-X中断机制。
pci_alloc_irq_vectors入参说明
|
参数 |
说明 |
|---|---|
|
dev |
目标 PCI 设备指针 |
|
min_vecs |
必需的最小中断数量(≥1),若分配失败则返回错误 |
|
max_vecs |
期望的最大中断数量,实际分配值可能小于此值 |
|
flags |
中断类型标志位组合 |
|
返回值 |
成功返回实际分配的中断数量 |
中断类型标志位:
-
PCI_IRQ_MSIX/MSI:允许尝试MSI-X/MSI中断;
-
PCI_IRQ_LEGACY:仅当min_vecs为1时可尝试INTx中断;
-
PCI_IRQ_AFFINITY:自动管理中断亲和性(负载均衡);
pci_irq_vectors
int pci_irq_vector(struct pci_dev *dev, unsigned int nr);
Linux 内核中用于 查询 PCI 设备实际分配的中断向量数量 的关键函数,通常与 pci_alloc_irq_vectors() 配合使用。其核心作用是获取设备成功分配的中断向量总数,从而为每个中断注册处理函数提供依据。
pci_irq_vectors函数用法简要示例:
int nvec = pci_alloc_irq_vectors(pdev, 1, MAX_VEC_NUM, PCI_IRQ_MSI); for (int i = 0; i < nvec; i++) { int irq = pci_irq_vector(pdev, i); request_irq(irq, irq_handler, 0, "dev_irq", dev_data); }
irq_set_affinity_hint
int irq_set_affinity_hint(unsigned int irq, const struct cpumask *mask);
Linux 内核中用于设置中断(IRQ)亲和性提示的函数,其主要作用是为特定中断建议一个优先处理的 CPU 核(或核集合),指导内核调度器在可能的情况下将中断绑定到指定 CPU 核上运行,从而优化多核系统的中断负载均衡和性能。
DMA测试
DMA常见的有两种模式,即Block模式与Linked-List模式。
Block模式与Linked-List模式对比
|
特性 |
Block模式 |
分散-聚集模式(Linked-List) |
|---|---|---|
|
数据分布 |
连续内存区域 |
非连续内存区域(多块分散数据) |
|
配置复杂度 |
简单(单次配置) |
复杂(需构建描述符链表) |
|
适用场景 |
大块连续数据 |
动态数据(网络包、视频帧分片) |
|
CPU负载 |
每次传输需重新配置 |
一次配置可完成多块传输 |
|
硬件要求 |
通用DMA控制器均支持 |
需支持描述符链表的高级控制器 |
Block模式
一种基础传输方式,适用于连续内存区域的大批量数据传输。
Linked-list模式
为了提高本地CPU搬运数据的效率,DMA提供了Linked-List模式,即链表模式。
LL模式中,DMA通过LL结构体获取源地址、目的地址以及长度等信息,每个LL结构体中又包含了Data Parts与Linked List Pointer。
Linked List Structure:
|
Data Part #0 |
Data Part #1 |
Data Part #2 |
… |
Linked List Pointer |
|---|

Data Part
RIE:Remote Interrupt Enable. 0 – disable, 1 – enable.
LIE: Local Interrupt Enable. 0 – disable, 1 – enable.

Linked List Pointer
参考资料
-
PCI_Express_Base_5.0r1.0.pdf;
-
MindShare__PCI_Express_System_Architecture.pdf;
欢迎关注我的独家微信公众号,定期为大家带来技术分享。

更多推荐



所有评论(0)