Linux下PCIe子系统(二)——PCIe子系统框架详解
Linux下PCIe子系统(二)——PCIe子系统框架详解
1. 概述
PCIe(PCI Express)子系统是Linux内核中负责管理PCI/PCIe设备的核心组件。它提供了一套完整的框架来发现、配置和管理PCI设备,实现了设备的即插即用和热插拔功能。
2. 核心数据结构
PCIe子系统使用以下核心数据结构来组织和管理设备:
2.1 主要数据结构
结构体 | 描述 | 作用 |
---|---|---|
struct pci_host_bridge | Host Bridge表示 | PCI域的根管理器,连接CPU和PCI总线 |
struct pci_bus | PCI总线表示 | 代表一条PCI总线,可以挂载多个设备 |
struct pci_dev | PCI设备表示 | 代表具体的PCI设备实例 |
2.2 数据结构关系
┌─────────────────────┐
│ pci_host_bridge │ ← Host Bridge (根管理器)
└──────────┬──────────┘│▼
┌─────────────────────┐
│ pci_bus │ ← PCI总线 (Bus 0)
│ (Root Bus) │
└──────────┬──────────┘│▼
┌─────────────────────┐
│ pci_dev │ ← PCI设备
│ (PCI设备/桥设备) │
└─────────────────────┘
3. 驱动框架基础
3.1 设备分类
Linux设备驱动将设备分为三大类:
- 字符设备 - 顺序访问的设备(如串口、键盘)
- 块设备 - 随机访问的设备(如硬盘、U盘)
- 网络设备 - 网络通信设备(如网卡)
3.2 字符设备注册流程
PCI设备通常以字符设备的形式加载到内核中,其注册流程包括:
- 注册设备号 - 获取主设备号和次设备号
- 创建并初始化cdev结构体 - 绑定file_operations
- 添加字符设备 - 将设备添加到内核
- 创建设备文件 - 在/dev下创建设备节点
- 实现file_operations - 实现设备操作函数集
注意: 学习PCIe子系统的目的是为专门的PCI设备开发对应的驱动程序。
4. PCI子系统实现流程
4.1 整体流程概览
4.2 时间线
启动时间线:
0ms 内核启动开始
2ms pci_driver_init() - PCI总线类型注册
5ms pcibios_init() - 架构相关初始化及设备枚举
8ms pci_subsys_init() - PCI子系统完善
15ms device_initcall() - 设备驱动匹配
20ms 用户空间启动
5. PCI子系统实现细节
5.1 PCI子系统初始化
5.1.1 pci_driver_init()函数
PCI子系统初始化通过pci_driver_init()
函数实现,在内核中创建PCI总线(软件抽象):
static int __init pci_driver_init(void)
{int ret;// 注册PCI总线类型到设备模型ret = bus_register(&pci_bus_type);if(ret)return ret;// 添加DMA调试支持dma_debug_add_bus(&pci_bus_type);return 0;
}
5.1.2 关键功能
- 总线注册: 调用
bus_register(&pci_bus_type)
(所有总线都通过此函数注册) - 设备模型集成: 将全局
pci_bus_type
结构体注册到内核设备模型 - sysfs接口: 在
/sys/bus/
下创建pci
目录,包含devices
和drivers
子目录
5.2 设备枚举机制
5.2.1 枚举 vs 设备树
传统设备树方式:
- 在设备树中描述硬件信息
- 通过
compatible
属性与驱动的of_match_table
匹配 - 静态配置,启动时解析一次
PCIe枚举方式:
- 动态发现设备
- 支持即插即用和热插拔
- 运行时设备管理
枚举定义: 在计算机硬件领域是指系统性地发现、识别和配置硬件设备的过程。
5.2.2 为什么PCIe使用枚举
使用设备树会破坏PCIe设备的核心优势:
- ❌ 即插即用 - 新设备无法自动识别
- ❌ 热插拔 - 运行时插拔设备无法处理
- ❌ 标准化 - 失去PCI协议的标准化优势
5.2.3 PCIe设备枚举详细流程
阶段一:基础设施建设
pci_driver_init() → 注册PCI总线类型
pcibios_init() → 平台特定PCI配置
pci_subsys_init() → 启动完整枚举流程
阶段二:Host Bridge注册和Root Bus创建
pci_scan_root_bus_bridge()
├── pci_register_host_bridge() // 注册Host Bridge
│ ├── 创建Root Bus (Bus 0)
│ ├── 分配地址空间资源
│ │ ├── 内存窗口
│ │ └── I/O端口窗口
│ └── 注册到Linux设备模型
└── pci_scan_child_bus() // 开始设备扫描
阶段三:递归设备扫描
核心扫描工作由pci_scan_child_bus_extend()
完成,采用深度优先递归算法:
pci_scan_child_bus_extend(bus, available_buses)
├── 第一阶段:设备扫描
│ └── for (devfn = 0; devfn < 0x100; devfn += 8) { // 32设备×8功能
│ ├── pci_scan_slot(bus, devfn)
│ ├── pci_scan_single_device()
│ ├── pci_scan_device() // 读取配置空间
│ └── pci_device_add() // 添加到系统
│ }
├── 第二阶段:桥设备处理
│ └── for_each_pci_bridge(dev, bus) {
│ ├── pci_add_new_bus() // 创建子总线
│ ├── 分配下级总线号
│ └── pci_scan_child_bus_extend(child_bus) // 递归!
│ }
└── 第三阶段:资源分配├── pci_bus_size_bridges() // 计算桥设备资源需求└── pci_bus_assign_resources() // 分配实际资源
5.2.4 设备发现机制
设备存在检测:
// 读取vendor ID判断设备是否存在
pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &vendor_id);if (vendor_id == 0xffffffff || vendor_id == 0x00000000) {// 设备不存在return NULL;
}
配置空间信息读取:
struct pci_dev *dev = pci_alloc_dev(bus);
dev->vendor = vendor_id & 0xffff;
dev->device = (vendor_id >> 16) & 0xffff;// 读取其他重要信息
pci_read_config_dword(dev, PCI_CLASS_REVISION, &class);
pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type);
// 读取BAR寄存器等...
5.2.5 递归扫描示例
扫描Root Bus 0:├── 00:00.0 - Host Bridge├── 00:01.0 - PCIe Root Port (桥设备)│ └── 创建Bus 1 → 递归扫描│ └── 01:00.0 - 显卡├── 00:02.0 - 集成显卡├── 00:1c.0 - PCIe Root Port (桥设备) │ └── 创建Bus 2 → 递归扫描│ └── 02:00.0 - 网卡└── 00:1f.0 - ISA桥└── 创建Bus 3 → 递归扫描
6. 设备驱动匹配机制
6.1 关键函数
PCIe设备与驱动的匹配依赖两个核心函数:
6.1.1 pci_bus_match()
功能:设备驱动匹配函数
static int pci_bus_match(struct device *dev, struct device_driver *drv)
{struct pci_dev *pci_dev = to_pci_dev(dev);struct pci_driver *pci_drv = to_pci_driver(drv);const struct pci_device_id *found_id;// 遍历驱动的设备ID表进行匹配found_id = pci_match_device(pci_drv, pci_dev);if (found_id)return 1; // 匹配成功return 0; // 匹配失败
}
匹配条件:
- Vendor ID: 厂商标识(由PCI-SIG分配)
- Device ID: 设备标识(厂商自定义)
- Subsystem Vendor ID: 子系统厂商ID(可选)
- Subsystem Device ID: 子系统设备ID(可选)
- Class Code: 设备类别(可选)
特殊值:
- 驱动中设置
PCI_ANY_ID
可匹配所有对应字段
6.1.2 pci_bus_probe()
功能:设备探测函数
static int pci_device_probe(struct device *dev)
{struct pci_dev *pci_dev = to_pci_dev(dev);struct pci_driver *drv = to_pci_driver(dev->driver);const struct pci_device_id *id;id = pci_match_device(drv, pci_dev);if (!id)return -ENODEV;// 调用驱动的probe函数return drv->probe(pci_dev, id);
}
6.2 匹配流程
6.3 vendor和device信息
存储位置:
- PCI配置空间: 偏移0x00(Vendor ID)和0x02(Device ID)
- 硬件固化: 设备制造时写入,无法修改
获取方式:
# 使用lspci查看设备信息
lspci -nn
# 输出示例:
# 00:02.0 VGA compatible controller [0300]: Intel Corporation Device [8086:9bc4]
# ^^^^ ^^^^
# vendor device
驱动中的使用:
static const struct pci_device_id my_pci_ids[] = {{ PCI_DEVICE(0x8086, 0x1234) }, // Intel的某个设备{ PCI_DEVICE(PCI_ANY_ID, PCI_ANY_ID) }, // 支持所有设备 { 0, } // 结束标记
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);
7. PCIe Host设备创建(设备树方式)
7.1 PCIe Host Controller的特殊性
虽然PCIe设备通过枚举发现,但PCIe Host Controller本身需要通过设备树描述:
- Host Controller: Platform设备,通过设备树描述
- PCI设备: 通过Host Controller枚举发现
7.2 设备树到Platform设备流程
7.2.1 设备树编译和加载
DTS源文件 → DTC编译器 → DTB二进制文件 → Bootloader加载到内存
7.2.2 DTS文件示例
pcie: pcie@f0000000 {compatible = "samsung,exynos5440-pcie";reg = <0xf0000000 0x1000>, <0xf0001000 0x1000>;interrupts = <0 5 0>, <0 4 0>;ranges = <0x81000000 0 0x60000000 0x60000000 0 0x00010000 /* I/O */0x82000000 0 0x60010000 0x60010000 0 0x0FFF0000>; /* Memory */#address-cells = <3>;#size-cells = <2>;device_type = "pci";
};
7.2.3 内核解析流程
start_kernel()└── setup_arch()└── unflatten_device_tree() // 展开设备树└── __unflatten_device_tree()└── populate_node() // 创建device_node结构
7.2.4 Platform设备创建
// 在core_initcall阶段
of_platform_default_populate_init()└── of_platform_default_populate()└── of_platform_bus_create()└── of_platform_device_create_pdata() // 创建platform_device
7.3 时间线对比
启动时间线:
0ms bootloader加载DTB到内存
2ms unflatten_device_tree() - 解析成device_node
5ms pci_driver_init() - PCI子系统初始化
10ms of_platform_default_populate() - 创建platform_device
12ms PCIe Host Controller驱动匹配和probe
15ms pci_scan_root_bus_bridge() - 开始PCI设备枚举
20ms PCI设备驱动匹配
8. 枚举过程关键特点
8.1 技术特性
- 自动发现性: 利用PCI协议的自描述特性,无需外部配置
- 层次化管理: 通过总线-桥-设备的树状结构组织
- 资源协调: 统一管理地址空间分配,避免资源冲突
- 热插拔支持: 为运行时设备插拔提供基础架构
8.2 扫描算法特点
- 广度优先: 同级设备并行扫描
- 深度递归: 发现桥设备时递归扫描下级总线
- 资源预留: 为桥设备预留足够的地址空间
- 错误处理: 优雅处理配置空间读取失败等异常
8.3 与其他总线对比
特性 | PCIe设备 | 设备树设备 |
---|---|---|
设备发现 | 硬件自动枚举 | 软件解析设备树 |
设备信息 | 配置空间自描述 | 设备树人工描述 |
匹配依据 | vendor/device ID | compatible字符串 |
热插拔 | 天然支持 | 通常不支持 |
标准化 | 完全标准化 | 需要自定义 |
9. 总结
9.1 PCIe子系统优势
PCIe子系统通过精心设计的层次化架构,实现了从硬件发现到驱动匹配的完整流程:
- 自动化设备发现: 通过枚举机制自动发现设备
- 标准化管理: 统一的数据结构和接口
- 即插即用支持: 支持热插拔和动态配置
- 层次化设计: 清晰的Host Bridge → Bus → Device架构
- 资源管理: 智能的地址空间分配和管理
9.2 关键技术点
- 递归扫描算法: 深度优先遍历整个PCI设备树
- 配置空间标准: 标准化的设备信息获取方式
- 双重机制: Host Controller用设备树,PCI设备用枚举
- 设备驱动分离: 清晰的设备发现和驱动匹配分离