PCIe 错误处理流程
AER与错误恢复机制详解
错误处理概述
PCIe提供了完善的错误检测、报告和恢复机制,确保数据传输的可靠性。AER(Advanced Error Reporting,高级错误报告)是PCIe错误处理的核心功能,提供了比基础错误报告更详细的错误信息。
为什么需要AER?
- 精确定位:识别错误发生的具体位置和类型
- 错误分类:区分可纠正错误和不可纠正错误
- 错误追溯:记录错误发生的次数和时间
- 系统恢复:支持软件介入进行错误恢复
错误类型分类
PCIe将错误分为两大类:可纠正错误(Correctable Errors)和不可纠正错误(Uncorrectable Errors)。
可纠正错误
可纠正错误是硬件可以自动恢复的错误,不需要软件干预。这类错误通常由链路噪声引起,通过重传机制解决。
| 错误类型 | 描述 | 处理方式 |
|---|---|---|
| Receiver Error | 接收端检测到物理层错误(8b/10b或128b/130b解码错误) | 物理层自动重传 |
| Bad TLP | TLP校验失败(LCRC错误或序列号错误) | 数据链路层重传 |
| Bad DLLP | DLLP CRC校验失败 | 丢弃并等待重传 |
| Replay Timeout | 重传缓冲区超时未收到ACK | 自动重传 |
| Replay Num Rollover | 重传次数超过阈值 | 链路重训练 |
| Advisory Non-Fatal | 非致命错误提示 | 记录日志,继续运行 |
不可纠正错误
不可纠正错误是硬件无法自动恢复的错误,需要软件介入处理。这类错误又分为非致命(Non-Fatal)和致命(Fatal)两种。
非致命错误
| 错误类型 | 描述 | 影响 |
|---|---|---|
| Poisoned TLP | 数据被标记为损坏(EP位设置) | 当前事务失败 |
| Unsupported Request | 接收到不支持的请求类型 | 返回UR完成状态 |
| Completer Abort | 完成方异常终止事务 | 返回CA完成状态 |
| Completion Timeout | 非Posted请求超时未收到完成包 | 事务失败 |
| Unexpected Completion | 收到未请求的完成包 | 丢弃完成包 |
| ACS Violation | 违反ACS访问控制规则 | 访问被拒绝 |
致命错误
| 错误类型 | 描述 | 影响 |
|---|---|---|
| Data Link Protocol Error | 数据链路层协议错误(序列号异常) | 链路需要复位 |
| Surprise Down | 链路意外断开 | 设备离线 |
| Malformed TLP | TLP格式错误(长度错误、保留位设置等) | 链路不稳定 |
| Flow Control Protocol Error | 流控制协议错误 | 数据传输中断 |
致命错误处理
致命错误通常表示链路或设备出现严重问题,需要:
- 禁用受影响的链路
- 进行链路重训练
- 可能需要系统重启
AER(高级错误报告)机制
AER能力结构
AER能力结构位于扩展配置空间中,起始偏移因设备而异(例如 0x140),通过能力链表遍历找到。它提供详细的错误报告功能,主要包括以下寄存器组:
可纠正错误寄存器组
| 寄存器 | 偏移 | 功能 |
|---|---|---|
| Correctable Error Status | 0x10 | 可纠正错误状态(每位对应一种错误) |
| Correctable Error Mask | 0x14 | 可纠正错误屏蔽(1=屏蔽) |
不可纠正错误寄存器组
| 寄存器 | 偏移 | 功能 |
|---|---|---|
| Uncorrectable Error Status | 0x04 | 不可纠正错误状态 |
| Uncorrectable Error Mask | 0x08 | 不可纠正错误屏蔽 |
| Uncorrectable Error Severity | 0x0C | 错误严重级别(0=非致命, 1=致命) |
错误日志寄存器组
| 寄存器 | 偏移 | 功能 |
|---|---|---|
| Advanced Error Capabilities & Control | 0x18 | AER 能力与控制(含 First Error Pointer) |
| Header Log | 0x1C-0x2B | 记录错误TLP的Header(4×32位) |
| Root Error Command | 0x2C | 控制错误报告方式(中断/日志,仅根口) |
| Root Error Status | 0x30 | Root Complex错误状态(仅根口) |
| Error Source ID | 0x34 | 报告错误的设备ID(仅根口) |
错误报告流程
下图按设备硬件 → Root Complex → 软件三个阶段,展示一个错误从检测到处理完成的完整路径:
无需软件参与
可纠正
硬件早已通过重传自动恢复,软件只需记录统计、清除状态
非致命
重试事务或通知上层,链路保持工作,清除状态后继续运行
致命
禁用链路并重训练,成功则重新初始化设备,失败则标记离线
※ 即使不使能中断,Root Error Status 也会置位,软件可通过轮询获取错误;是否产生中断由 Root Error Command 寄存器控制。
错误恢复流程
可纠正错误恢复
可纠正错误由硬件自动恢复,软件只需要记录和监控。
// 可纠正错误处理伪代码
void handle_correctable_error(struct pci_dev *pdev) {
u32 status = read_aer_status(pdev);
// 记录错误统计
if (status & RECEIVER_ERROR)
pdev->err_stats.receiver_err++;
if (status & BAD_TLP)
pdev->err_stats.bad_tlp++;
// 清除错误状态
write_aer_status(pdev, status);
// 如果错误频率过高,记录警告
if (pdev->err_stats.receiver_err > THRESHOLD) {
log_warning("High error rate on %s", pdev->name);
}
}
非致命错误恢复
非致命错误需要软件介入,但通常不需要复位链路。
// 非致命错误处理伪代码
void handle_nonfatal_error(struct pci_dev *pdev) {
u32 status = read_aer_uncorr_status(pdev);
if (status & COMPLETION_TIMEOUT) {
// 完成超时,需要重试事务
retry_transaction(pdev);
}
if (status & UNSUPPORTED_REQUEST) {
// 不支持的请求,记录并通知上层
log_error("Unsupported request from %s", pdev->name);
notify_upper_layer(pdev);
}
if (status & POISONED_TLP) {
// 数据损坏,需要重新获取数据
handle_poisoned_data(pdev);
}
// 清除错误状态
write_aer_uncorr_status(pdev, status);
}
致命错误恢复
致命错误需要链路复位,可能导致数据丢失。
// 致命错误处理伪代码(对应驱动实现 pci_error_handlers 回调组)
void handle_fatal_error(struct pci_dev *pdev) {
u32 status = read_aer_uncorr_status(pdev);
if (status & (SURPRISE_DOWN | DATA_LINK_PROTOCOL)) {
// 停止访问设备(撤销 MMIO/DMA 使能)
pci_disable_device(pdev);
// 尝试 Secondary Bus Reset 复位整条总线(PCI core 真实 API)
if (pci_reset_bus(pdev) == 0) {
// 复位成功,重新使能并初始化设备
pci_reenable_device(pdev);
} else {
// 复位失败(如链路已死),标记设备离线
pdev->state = DEVICE_OFFLINE;
notify_system_admin(pdev);
}
}
// 清除错误状态
write_aer_uncorr_status(pdev, status);
}
Linux AER驱动
Linux内核提供了AER驱动(aerdrv),自动处理PCIe错误。
Linux AER相关命令
# 查看AER错误计数
$ lspci -vvv -s 00:01.0 | grep -A 20 "Advanced Error Reporting"
# 查看系统日志中的PCIe错误
$ dmesg | grep -i "pci.*error"
# AER 由内核 pcie/aer 驱动自动使能与处理,无需手动开启
# 每设备的 AER 错误计数(内核 4.19+ 自动暴露)
$ cat /sys/bus/pci/devices/0000:00:01.0/aer_dev_correctable
$ cat /sys/bus/pci/devices/0000:00:01.0/aer_dev_fatal
$ cat /sys/bus/pci/devices/0000:00:01.0/aer_dev_nonfatal
# 持久记录与用户态解码:rasdaemon 监听 AER tracepoint
# 错误注入:aer_inject 模块 + 用户态 aer-inject 工具(见 pcieaer-howto 文档)
复位层级:从上电到 FLR
错误恢复最终都要落到"复位"上。PCIe 的复位不是单一动作,而是一套作用范围、副作用、时序各不相同的层级——选错层级是错误恢复失败的常见根因。
| 复位类型 | 触发方式 | 作用范围 | 时序要求 |
|---|---|---|---|
| 冷复位(Cold Reset) | 主电源/辅助电源上电 | 整条链路 | 重新上电,LTSSM 从 Detect 重来 |
| 热复位(Warm Reset) | RST# 引脚或 RESET# 带内触发 | 整条链路 | 状态机回 Detect,重新训练 |
| Hot Reset | 带内:TS1 Ordered Set,Hot Reset 位=1 | 下游链路 | LTSSM 进入 Hot Reset 态;SW 可经桥的 Bridge Control 寄存器 bit6(Secondary Bus Reset)触发 |
| Secondary Bus Reset(SBR) | 软件写上游口 Bridge Control 的 Secondary Bus Reset 位 | 该口下游总线 | 内核 pci_reset_bus() 的核心动作;保持 ≥1ms |
| FLR(Function Level Reset) | 软件写 DevCtl 的 Initiate FLR(bit15),或 pcie_reset_flr() |
仅单个 Function,不影响链路与其他 Function | 设备须在 100ms 内完成复位;期间配置访问返回 CRS/全 1 需妥善处理 |
FLR 的软件视角
FLR 是 SR-IOV/热迁移场景的主力复位:只复位一个 VF/Function,不影响同一设备的其他 Function。内核统一入口:echo 1 > /sys/bus/pci/devices/<BDF>/reset 会按设备能力自动选择 FLR / SBR / PM reset(pci_reset_function());驱动也可实现 .reset 回调在复位前后保存恢复私有状态。FLR 的两个经典坑:复位后配置空间读取可能得到 CRS(Config Request Retry Status),重试逻辑缺失会把"未就绪"误判为"设备消失";DMA 通道必须在触发 FLR 前停干净,否则复位期间的在途写会打到已释放的内存。
驱动侧错误恢复:pci_error_handlers
AER 驱动把错误上报给内核后,错误恢复的"最后一公里"由设备驱动完成。驱动通过 struct pci_error_handlers 参与一个四阶段状态机:
/* drivers/pci/pci.h 中定义的错误恢复回调组 */
static pci_ers_result_t my_error_detected(struct pci_dev *pdev,
pci_channel_state_t state)
{
/* 阶段1:错误上报。state:
pci_channel_io_normal — 可纠正/建议性错误,驱动可继续工作
pci_channel_io_frozen — 非致命错误,停止发起新 MMIO/ DMA
pci_channel_io_perm_failure — 致命错误,设备不可达 */
switch (state) {
case pci_channel_io_normal:
/* 清除设备内部错误状态即可 */
my_clear_internal_errors(pdev);
return PCI_ERS_RESULT_CAN_RECOVER;
case pci_channel_io_frozen:
my_stop_queues(pdev); /* 停队列、停 DMA,等待后续裁决 */
return PCI_ERS_RESULT_NEED_RESET;
default:
return PCI_ERS_RESULT_DISCONNECT;
}
}
static pci_ers_result_t my_slot_reset(struct pci_dev *pdev)
{
/* 阶段2:内核已对设备/槽位完成复位(SBR/FLR),
驱动重新初始化设备(相当于一次精简版 probe) */
if (my_reinit_hw(pdev))
return PCI_ERS_RESULT_DISCONNECT;
my_start_queues(pdev);
return PCI_ERS_RESULT_RECOVERED;
}
static void my_resume(struct pci_dev *pdev)
{
/* 阶段3:恢复完成,重新对外服务 */
my_resume_upper_layers(pdev);
}
static const struct pci_error_handlers my_err_handlers = {
.error_detected = my_error_detected,
.slot_reset = my_slot_reset,
.resume = my_resume,
.cor_error_detected = my_cor_error_detected, /* 可纠正错误通知(可选) */
};
| 回调 | 时机 | 返回值 |
|---|---|---|
.error_detected() | AER 解析出不可纠正错误后第一时间;MMIO 访问已按 channel 状态被拦截 | CAN_RECOVER / NEED_RESET / DISCONNECT |
.mmio_enabled() | 可恢复路径下,内核重新放行 MMIO 后 | RECOVERED / NEED_RESET |
.slot_reset() | 内核完成复位(SBR/FLR)之后 | RECOVERED / DISCONNECT |
.resume() | 全部设备恢复后(仅 RECOVERED 的设备收到) | void |
.reset_prepare() / .reset_done() | 用户态/内核触发函数级复位的前后 | void(用于驱动自保状态) |
DPC:把致命错误"扼杀"在端口上
DPC(Downstream Port Containment)是 DPC Extended Capability(Cap ID 0x001D,见本页上方 AER 表的邻居)提供的硬件遏制机制:当下游链路出现不可纠正错误(含 Surprise Down),端口立即把链路拉到电气空闲、不经软件直接遏制错误扩散,同时触发 DPC 中断通知内核。内核的 DPC 驱动随后按 AER 一样的错误恢复流程(pci_error_handlers)复位下游并恢复设备。未实现 DPC 的系统里,Surprise Link Down 往往表现为一批总线错误 + 悬空的设备;实现后则是一条清晰的"DPC 事件 → 槽位复位 → resume"链路。
驱动接入错误恢复的检查单
- 实现
pci_error_handlers三主回调;可纠正错误如需感知再挂.cor_error_detected; error_detected里只停不修:停 DMA/停队列,不尝试访问可能已死的 MMIO;slot_reset里的重初始化要能容忍 CRS 重试(复位后设备可能未就绪);- 上层协议状态(NVMe 队列、网卡收发环)的恢复与设备恢复解耦,
resume阶段再重启; - 测试路径:aer_inject 注入不可纠正错误 → dmesg 观察四阶段回调依次触发(本页下方错误注入测试)。
错误注入测试
错误注入是验证系统错误处理能力的重要手段。PCIe提供了多种错误注入方法。
软件错误注入
通过写AER寄存器模拟错误。
// 注入可纠正错误
void inject_correctable_error(struct pci_dev *pdev, u32 error_type) {
// 设置错误注入寄存器
pci_write_config_dword(pdev, AER_INJECT_CORR, error_type);
// 触发错误
pci_write_config_dword(pdev, AER_INJECT_CONTROL, INJECT_ENABLE);
}
// 注入不可纠正错误
void inject_uncorrectable_error(struct pci_dev *pdev, u32 error_type) {
pci_write_config_dword(pdev, AER_INJECT_UNCORR, error_type);
pci_write_config_dword(pdev, AER_INJECT_CONTROL, INJECT_ENABLE);
}
硬件错误注入工具
PCIe Jammer
专业PCIe协议分析仪,支持错误注入功能
FPGA测试平台
使用FPGA实现自定义错误注入逻辑
Linux aer-inject
内核提供的AER错误注入工具
测试用例设计
| 测试项 | 注入错误类型 | 预期结果 |
|---|---|---|
| 可纠正错误恢复 | Receiver Error, Bad TLP | 硬件自动恢复,错误计数增加 |
| 完成超时处理 | Completion Timeout | 驱动重试事务 |
| 数据损坏处理 | Poisoned TLP | 上层应用收到错误通知 |
| 链路恢复 | Surprise Down | 链路重训练成功 |
| 错误屏蔽 | 设置Error Mask | 被屏蔽的错误不产生中断 |
错误处理最佳实践
监控与告警
- 定期检查AER错误计数器
- 设置错误率阈值告警
- 记录错误趋势用于预测性维护
错误屏蔽策略
- 屏蔽已知的良性错误(如热插拔期间的瞬态错误)
- 不要屏蔽致命错误
- 生产环境谨慎使用错误屏蔽
恢复策略
- 实现分层恢复(事务级→链路级→设备级)
- 设置恢复重试次数上限
- 记录恢复失败事件
测试验证
- 定期进行错误注入测试
- 验证错误恢复时间满足SLA
- 测试极端错误场景
总结
PCIe错误处理机制是系统可靠性的重要保障。通过AER提供的详细错误信息,结合合理的错误分类和恢复策略,可以构建高可用性的PCIe系统。
关键要点
- 理解可纠正错误和不可纠正错误的区别
- 正确配置AER寄存器以获得详细的错误信息
- 实现分层的错误恢复策略
- 定期进行错误注入测试验证系统鲁棒性
- 监控错误趋势,进行预测性维护