错误处理概述

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 → 软件三个阶段,展示一个错误从检测到处理完成的完整路径:

设备硬件自动完成
无需软件参与
1
错误检测
物理层 / 数据链路层 / 事务层检测到异常(LCRC 校验失败、Replay 超时、协议违例……)
2
状态记录硬件自动置位
置位 Error Status 对应的位,并把出错 TLP 的 Header 存入 Header Log(0x1C–0x2B)供事后分析
3
发送错误消息 TLP按错误类型三选一
ERR_COR ERR_NONFATAL ERR_FATAL
错误消息 TLP 经 Switch 逐级上传至 Root Complex
Root Complex接收与通知
4
产生中断通知Root Error Status 置位
RC 把错误源 ID 记入 Error Source ID(0x34),并按 Root Error Command(0x2C)的配置产生 MSI / INTx 中断
中断触发 AER 中断服务程序
软件AER 驱动处理
5
读取寄存器,按严重级别分发处理
读取 Error Status 判断错误类型,结合 Header Log 还原出错的 TLP,然后分三路处理:
可纠正

硬件早已通过重传自动恢复,软件只需记录统计、清除状态

非致命

重试事务或通知上层,链路保持工作,清除状态后继续运行

致命

禁用链路并重训练,成功则重新初始化设备,失败则标记离线

※ 即使不使能中断,Root Error Status 也会置位,软件可通过轮询获取错误;是否产生中断由 Root Error Command 寄存器控制。

用错误掩码解码器查询 AER 错误
拿到 Uncorrectable/Correctable Error Status 的十六进制值后,打开错误查询工具,逐位解码出对应的错误名称与含义。

错误恢复流程

可纠正错误恢复

可纠正错误由硬件自动恢复,软件只需要记录和监控。

// 可纠正错误处理伪代码
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寄存器以获得详细的错误信息
  • 实现分层的错误恢复策略
  • 定期进行错误注入测试验证系统鲁棒性
  • 监控错误趋势,进行预测性维护

参考资源

理解检测
1. 以下哪种 PCIe 错误属于可纠正错误(Correctable Error)?
Bad TLP 是可纠正错误,由数据链路层通过重传机制自动恢复。Completion Timeout、Poisoned TLP 属于非致命不可纠正错误,需要软件介入。Surprise Down 属于致命错误,需要链路复位。
2. AER 的 Uncorrectable Error Severity 寄存器(偏移 0x0C)的作用是什么?
Uncorrectable Error Severity 寄存器允许软件为每种不可纠正错误配置严重级别:0 = 非致命(Non-Fatal),1 = 致命(Fatal)。这提供了灵活性,例如可以将某些默认致命错误降级为非致命,避免不必要的链路复位。
3. 当 PCIe 设备发生致命错误(如 Data Link Protocol Error)时,系统应该采取什么恢复策略?
致命错误表示链路或设备出现严重问题。恢复流程为:禁用受影响的链路 → 尝试链路重训练 → 如果成功则重新初始化设备 → 如果失败则标记设备离线并通知系统管理员。不会自动重传 TLP(那是可纠正错误的处理方式),也不需要重启整个操作系统。