当前位置: 首页 > news >正文

Linux下PCIe子系统(二)——PCIe子系统框架详解

Linux下PCIe子系统(二)——PCIe子系统框架详解

1. 概述

PCIe(PCI Express)子系统是Linux内核中负责管理PCI/PCIe设备的核心组件。它提供了一套完整的框架来发现、配置和管理PCI设备,实现了设备的即插即用和热插拔功能。

2. 核心数据结构

PCIe子系统使用以下核心数据结构来组织和管理设备:
在这里插入图片描述

2.1 主要数据结构

结构体描述作用
struct pci_host_bridgeHost Bridge表示PCI域的根管理器,连接CPU和PCI总线
struct pci_busPCI总线表示代表一条PCI总线,可以挂载多个设备
struct pci_devPCI设备表示代表具体的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设备通常以字符设备的形式加载到内核中,其注册流程包括:

  1. 注册设备号 - 获取主设备号和次设备号
  2. 创建并初始化cdev结构体 - 绑定file_operations
  3. 添加字符设备 - 将设备添加到内核
  4. 创建设备文件 - 在/dev下创建设备节点
  5. 实现file_operations - 实现设备操作函数集

注意: 学习PCIe子系统的目的是为专门的PCI设备开发对应的驱动程序。

4. PCI子系统实现流程

4.1 整体流程概览

内核启动
各子系统初始化
PCI子系统初始化
创建PCI总线结构
设备枚举
扫描PCI设备并创建设备对象
驱动加载
PCI驱动注册并匹配设备

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目录,包含devicesdrivers子目录

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 匹配流程

匹配成功
匹配失败
设备/驱动注册
pci_bus_match调用
比对vendor/device信息
pci_bus_probe调用
尝试下一个驱动
pci_device_probe执行
调用驱动probe函数
设备驱动绑定完成
匹配失败

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 IDcompatible字符串
热插拔天然支持通常不支持
标准化完全标准化需要自定义

9. 总结

9.1 PCIe子系统优势

PCIe子系统通过精心设计的层次化架构,实现了从硬件发现到驱动匹配的完整流程:

  1. 自动化设备发现: 通过枚举机制自动发现设备
  2. 标准化管理: 统一的数据结构和接口
  3. 即插即用支持: 支持热插拔和动态配置
  4. 层次化设计: 清晰的Host Bridge → Bus → Device架构
  5. 资源管理: 智能的地址空间分配和管理

9.2 关键技术点

  • 递归扫描算法: 深度优先遍历整个PCI设备树
  • 配置空间标准: 标准化的设备信息获取方式
  • 双重机制: Host Controller用设备树,PCI设备用枚举
  • 设备驱动分离: 清晰的设备发现和驱动匹配分离
http://www.dtcms.com/a/273305.html

相关文章:

  • 算法练习5-原地移除数组中所有的元素
  • 多元函数的链式法则:从单变量到高维的推广
  • 无人设备遥控器之无线电频率篇
  • Java HashMap已存在的值是否覆盖
  • 全链智造铸丰碑 全球布局启新程 —— 河北华创测控技术有限公司领航测控产业新高度
  • python学习打卡:DAY 29 复习日:类的装饰器
  • 快捷键——VsCode
  • msf复现“永恒之蓝”
  • 在 node 端执行打开一个新的 chrome 并且跳转到指定 url
  • 力扣热门算法题 136.只出现一次的数字,139.单词拆分,141.环形链表
  • Docker——容器文件操作
  • 实习第一个小需求样式问题总结
  • 八猴渲染器三维场景实时预览软件 Marmoset Toolbag 5.02b1 Win
  • 20250710-day11
  • 如何选择合适的ai降重工具?七个实用的ai查重网站
  • MyBatis插件机制揭秘:从拦截器开发到分页插件实战
  • 「莫尔物理新范式」普林斯顿马普所合作Nature论文:SnSe₂/ZrS₂扭曲双层实现M点能谷调控与拓扑新效应
  • 安装VMware详细步骤
  • 基于模糊控制及BP神经网络开关磁阻电机的matlab仿真
  • Python-函数进阶
  • 国内如何考取Oracle大师
  • F-GNN的新型检测框架:随机森林增强图神经网络
  • JDK动态代理:深入解析Java动态代理的核心实现
  • qwen3、gemma3 GPRO强化训练案例
  • spring-ai agent概念
  • 6.4 BL2到BL31/BL33的切换
  • Android 13----在framworks层映射一个物理按键
  • C++并发编程-12. 用内存顺序实现内存模型
  • 写《XX顶层设计》和《XX可研报告》区别。
  • MySQL索引:数据库的超级目录