目录
3.5.1、pcie_do_recovery 整体总结--AER处理的核心部分
3.5.2、附加: aer_root_reset的函数分析--辅助理解pcie_do_recovery
一、AER内核处理整体流程梳理
可能有理解不到位写得不对的地方
二、AER代码重要部分梳理
AER驱动与pciehp、pcie-dpc类似,都是作为PCIe port的可选服务,这些服务模块挂载在PCIe port驱动上,由portdrv_core统一管理。服务的注册通过pcie_port_service_register函数完成:
1、AER初始化阶段
static struct pcie_port_service_driver aerdriver = { .name = "aer", .port_type = PCIE_ANY_PORT, .service = PCIE_PORT_SERVICE_AER, .probe = aer_probe, .remove = aer_remove, }; int __init pcie_aer_init(void) { if (!pci_aer_available()) return -ENXIO; return pcie_port_service_register(&aerdriver); }
PCIe AER驱动属于PCIe port driver, 其绑定的是PCIe root port
同时,port_type = PCIE_ANY_PORT这个澄清内核增加了PCIE RCEC支持,针对RCEC设备也可以AER处理
static int aer_probe(struct pcie_device *dev) { ... ... /* * AER 仅支持根端口(Root Port)或根复合体事件收集器(RCEC) * 检查PCIe设备类型,如果不是这两种类型则直接返回 */ if ((pci_pcie_type(port) != PCI_EXP_TYPE_RC_EC) && (pci_pcie_type(port) != PCI_EXP_TYPE_ROOT_PORT)) return -ENODEV; // 设备不支持 /* 为根端口控制结构(aer_rpc)分配内核内存 */ rpc = devm_kzalloc(device, sizeof(struct aer_rpc), GFP_KERNEL); if (!rpc) return -ENOMEM; /* 内存分配失败 */ /* 初始化aer_rpc结构体 */ rpc->rpd = port; /* 保存根端口设备 */ INIT_KFIFO(rpc->aer_fifo); /* 初始化用于AER事件的FIFO队列 */ set_service_data(dev, rpc); /* 将rpc与pcie_device关联 */ status = devm_request_threaded_irq(device, dev->irq, aer_irq, aer_isr, IRQF_SHARED, "aerdrv", dev); ... ... /* 在根端口上启用AER功能 */ aer_enable_rootport(rpc); ... ... }
主要做了下面两件事情:
(1)注册AER事件的线程化中断处理程序:aer_irq: 上半部(快速处理),aer_isr: 下半部(实际处理)
(2)在根端口上启用AER功能
2、中断上半部 aer_irq
static irqreturn_t aer_irq(int irq, void *context) { ... ... // 读取根错误状态寄存器 pci_read_config_dword(rp, aer + PCI_ERR_ROOT_STATUS, &e_src.status); // 检查是否真的有错误发生(可纠正或不可纠正错误) if (!(e_src.status & (PCI_ERR_ROOT_UNCOR_RCV|PCI_ERR_ROOT_COR_RCV))) return IRQ_NONE; // 如果没有错误,返回IRQ_NONE表示不是我们的中断 // 读取错误源ID寄存器,获取详细错误信息 pci_read_config_dword(rp, aer + PCI_ERR_ROOT_ERR_SRC, &e_src.id); // 清除根错误状态寄存器(写1清除) pci_write_config_dword(rp, aer + PCI_ERR_ROOT_STATUS, e_src.status); // 尝试将错误信息放入FIFO队列 if (!kfifo_put(&rpc->aer_fifo, e_src)) return IRQ_HANDLED; // 如果队列已满,直接返回IRQ_HANDLED // 成功放入队列,返回IRQ_WAKE_THREAD唤醒下半部处理线程 return IRQ_WAKE_THREAD; }
这是中断处理的上半部,主要负责快速读取错误状态并暂存数据,实际处理会在下半部中进行,主要做了下面几件事:
(1)通过PCI_ERR_ROOT_STATUS寄存器检测错误类型
(2)读取PCI_ERR_ROOT_ERR_SRC获取错误源详细信息
(3)清除错误状态,唤醒中断下半部
3、中断下半部 aer_isr
3.1、aer_isr_one_error
上半部 aer_irq 将错误存入FIFO,下半部 aer_isr 是消费者,从FIFO取出错误处理
static irqreturn_t aer_isr(int irq, void *context) { ... ... // 循环处理FIFO中的所有错误信息 while (kfifo_get(&rpc->aer_fifo, &e_src)) { // 对每个错误调用处理函数 aer_isr_one_error(rpc, &e_src); } return IRQ_HANDLED; } static void aer_isr_one_error(struct aer_rpc *rpc, struct aer_err_source *e_src) { ... ... if (e_src->status & PCI_ERR_ROOT_COR_RCV) { // 设置可纠正错误信息 e_info.id = ERR_COR_ID(e_src->id); // 提取可纠正错误ID e_info.severity = AER_CORRECTABLE; // 设置错误严重性为可纠正 // 检查是否多个可纠正错误 if (e_src->status & PCI_ERR_ROOT_MULTI_COR_RCV) e_info.multi_error_valid = 1; // 标记为多个错误 else e_info.multi_error_valid = 0; // 打印端口错误信息 aer_print_port_info(pdev, &e_info); // 查找错误源设备并处理错误 if (find_source_device(pdev, &e_info)) aer_process_err_devices(&e_info); } // 处理不可纠正错误 if (e_src->status & PCI_ERR_ROOT_UNCOR_RCV) { // 设置不可纠正错误信息 e_info.id = ERR_UNCOR_ID(e_src->id); // 提取不可纠正错误ID // 判断是否为致命错误 if (e_src->status & PCI_ERR_ROOT_FATAL_RCV) e_info.severity = AER_FATAL; // 致命错误 else e_info.severity = AER_NONFATAL; // 非致命错误 // 检查是否多个不可纠正错误 if (e_src->status & PCI_ERR_ROOT_MULTI_UNCOR_RCV) e_info.multi_error_valid = 1; // 标记为多个错误 else e_info.multi_error_valid = 0; // 打印端口错误信息 aer_print_port_info(pdev, &e_info); // 查找错误源设备并处理错误 if (find_source_device(pdev, &e_info)) aer_process_err_devices(&e_info); } }
错误处理流程
(1)先更新错误统计(pci_rootport_aer_stats_incr)
(2)打印错误信息(aer_print_port_info)
(3)定位错误源设备(find_source_device)
(4)处理错误设备(aer_process_err_devices)
3.2、find_source_device
static bool find_source_device(struct pci_dev *parent, struct aer_err_info *e_info) { ... ... /* 检查根端口本身是否是发送错误消息的代理 */ result = find_device_iter(dev, e_info); if (result) return true; /* 根据父设备类型采用不同的搜索方式 */ if (pci_pcie_type(parent) == PCI_EXP_TYPE_RC_EC) /* 如果是根复合体事件收集器(RCEC),则遍历RCEC */ pcie_walk_rcec(parent, find_device_iter, e_info); else /* 否则遍历根端口的下属总线 */ pci_walk_bus(parent->subordinate, find_device_iter, e_info); ... ... }
在PCIe设备树中定位触发AER(高级错误报告)的具体设备,支持从根端口(Root Port)或根复合体事件收集器(RCEC)开始搜索。
首先检查根端口自身是否是错误源,如果不是,则向下遍历设备树:
对于RCEC类型设备使用pcie_walk_rcec()
对于普通根端口使用pci_walk_bus()
3.3、aer_process_err_devices
static inline void aer_process_err_devices(struct aer_err_info *e_info) { int i; /* 第一阶段:报告所有错误信息(在处理前先记录,避免因复位等操作丢失记录) */ for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { if (aer_get_device_error_info(e_info->dev[i], e_info)) aer_print_error(e_info->dev[i], e_info); } /* 第二阶段:处理所有错误源 */ for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { if (aer_get_device_error_info(e_info->dev[i], e_info)) handle_error_source(e_info->dev[i], e_info); } }
该函数主要负责两个阶段处理错误设备:
(1)错误信息报告阶段:先收集并打印所有设备的错误信息
(2)错误处理阶段:然后对所有设备执行实际的错误处理
3.4、handle_error_source
static void handle_error_source(struct pci_dev *dev, struct aer_err_info *info) { /* 获取设备的AER能力寄存器偏移量 */ int aer = dev->aer_cap; /* 处理可纠正错误(AER_CORRECTABLE)*/ if (info->severity == AER_CORRECTABLE) { // 可纠正错误不需要软件干预,无需走完整的错误恢复流程。 if (aer) /* 清除可纠正错误状态寄存器(写1清除) */ pci_write_config_dword(dev, aer + PCI_ERR_COR_STATUS, info->status); /* 如果设备支持原生AER处理,清除设备状态 */ if (pcie_aer_is_native(dev)) pcie_clear_device_status(dev); } /* 处理非致命错误(AER_NONFATAL)*/ else if (info->severity == AER_NONFATAL) /* 执行标准恢复流程(I/O通道状态正常) */ pcie_do_recovery(dev, pci_channel_io_normal, aer_root_reset); /* 处理致命错误(AER_FATAL)*/ else if (info->severity == AER_FATAL) /* 执行强制恢复流程(I/O通道已冻结) */ pcie_do_recovery(dev, pci_channel_io_frozen, aer_root_reset); /* 减少设备的引用计数(配对之前可能的pci_dev_get) */ pci_dev_put(dev); }
函数根据错误严重级别采取不同的处理措施:
可纠正错误:仅清除错误状态寄存器,额外调用pcie_clear_device_status
确保状态清除
非致命和致命错误都调用pcie_do_recovery
,但传入不同的I/O通道状态:
pci_channel_io_normal:链路仍可用
pci_channel_io_frozen:链路已冻结
非致命错误:触发普通恢复流程
致命错误:触发强制恢复流程
3.5、pcie_do_recovery 整体逻辑
下面这块逻辑比较隐晦:
pci_ers_result_t pcie_do_recovery(struct pci_dev *dev, pci_channel_state_t state, pci_ers_result_t (*reset_subordinates)(struct pci_dev *pdev)) { /* * - 如果是根端口/下游端口/RCEC/RCiEP,恢复该设备及其下级设备 * - 其他设备类型,恢复该设备及同端口下的所有设备 */ if (type == PCI_EXP_TYPE_ROOT_PORT || type == PCI_EXP_TYPE_DOWNSTREAM || type == PCI_EXP_TYPE_RC_EC || type == PCI_EXP_TYPE_RC_END) bridge = dev; // 端口类设备自身作为恢复起点 else bridge = pci_upstream_bridge(dev); // 其他设备向上找到最近的端口 ... ... /* 阶段1:错误检测处理 */ if (state == pci_channel_io_frozen) { /* 冻结状态处理 */ pci_walk_bridge(bridge, report_frozen_detected, &status); if (reset_subordinates(bridge) != PCI_ERS_RESULT_RECOVERED) { pci_warn(bridge, "下级设备重置失败\n"); goto failed; } } else { /* 正常状态处理 */ pci_walk_bridge(bridge, report_normal_detected, &status); } /* 阶段2:MMIO重新启用 */ if (status == PCI_ERS_RESULT_CAN_RECOVER) { status = PCI_ERS_RESULT_RECOVERED; pci_walk_bridge(bridge, report_mmio_enabled, &status); } /* 阶段3:插槽重置处理 */ if (status == PCI_ERS_RESULT_NEED_RESET) { // 插槽重置函数,然后再调用, 驱动的slot_reset回调 status = PCI_ERS_RESULT_RECOVERED; pci_walk_bridge(bridge, report_slot_reset, &status); } ... /* 阶段4:恢复完成处理 */ pci_walk_bridge(bridge, report_resume, &status); // 如果OS原生控制AER,清除设备错误状态; 如果平台控制AER,由平台负责清除 if (host->native_aer || pcie_ports_native) { pcie_clear_device_status(dev); pci_aer_clear_nonfatal_status(dev); } ... ... }
首先是 pci_walk_bridge(bridge, report_frozen_detected, &status) 这块比较绕,展开后可以发现:
static void pci_walk_bridge(struct pci_dev *bridge, int (*cb)(struct pci_dev *, void *), void *userdata) { if (bridge->subordinate) pci_walk_bus(bridge->subordinate, cb, userdata); else cb(bridge, userdata); } void pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *), void *userdata) { struct pci_dev *dev; struct pci_bus *bus; struct list_head *next; int retval; bus = top; down_read(&pci_bus_sem); next = top->devices.next; for (;;) { if (next == &bus->devices) { /* end of this bus, go up or finish */ if (bus == top) break; next = bus->self->bus_list.next; bus = bus->self->bus; continue; } dev = list_entry(next, struct pci_dev, bus_list); if (dev->subordinate) { /* this is a pci-pci bridge, do its devices next */ next = dev->subordinate->devices.next; bus = dev->subordinate; } else next = dev->bus_list.next; retval = cb(dev, userdata); if (retval) break; } up_read(&pci_bus_sem); }
首先分析 pci_walk_bus,该函数主要做了以下两件事:
(1)优先向下遍历桥接设备的子总线,确保处理完整个子树后再返回上级,如:
Bus 0 (top) ├─ Device A(桥接器)→ Bus 1 │ ├─ Device C │ └─ Device D └─ Device B
遍历顺序:Bus 0 → Device A → Bus 1 → Device C → Device D → Device B
(2)回调函数cb (即report_frozen_detected)返回非零值会立即终止遍历(例如在错误恢复中已找到目标设备时)。
下面再看 report_frozen_detected这块调用流程的逻辑:
static int report_frozen_detected(struct pci_dev *dev, void *data) { return report_error_detected(dev, pci_channel_io_frozen, data); } static int report_error_detected(struct pci_dev *dev, pci_channel_state_t state, enum pci_ERS_result *result) { const struct pci_error_handlers *err_handler; ... ... if (!pci_dev_set_io_state(dev, state) || !dev->driver || !dev->driver->err_handler || !dev->driver->err_handler->error_detected) { /* 如果整个设备subtree没有error_detected回调, PCI_ERS_RESULT_NO_AER_DRIVER将阻止后续任何设备的错误回调 */ if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) { /* 非桥设备没有回调则标记为无法恢复 */ vote = PCI_ERS_RESULT_NO_AER_DRIVER; } else { vote = PCI_ERS_RESULT_NONE; } } else { /* 获取错误处理程序并调用error_detected回调 */ err_handler = dev->driver->err_handler; vote = err_handler->error_detected(dev, state); } ... ... } static pci_ers_result_t merge_result(enum pci_ers_result orig, enum pci_ers_result new) { if (new == PCI_ERS_RESULT_NO_AER_DRIVER) return PCI_ERS_RESULT_NO_AER_DRIVER; if (new == PCI_ERS_RESULT_NONE) return orig; switch (orig) { case PCI_ERS_RESULT_CAN_RECOVER: case PCI_ERS_RESULT_RECOVERED: orig = new; break; case PCI_ERS_RESULT_DISCONNECT: if (new == PCI_ERS_RESULT_NEED_RESET) orig = PCI_ERS_RESULT_NEED_RESET; break; default: break; } return orig; }
可以发现,这个函数的主要作用是遍历PCI总线上的多个设备时(例如通过 pci_walk_bus
),综合所有设备的错误恢复状态,决定最终的恢复策略(如是否需要复位、是否断开设备等),根据代码看,整体的设备是否可恢复状态合并逻辑是这样的:
原始状态 (orig) | 新状态 (new) | 合并结果 |
---|---|---|
CAN_RECOVER |
DISCONNECT |
DISCONNECT |
RECOVERED |
NEED_RESET |
NEED_RESET |
DISCONNECT |
NEED_RESET |
NEED_RESET |
DISCONNECT |
CAN_RECOVER |
DISCONNECT (不降级) |
NO_AER_DRIVER |
任意 | NO_AER_DRIVER (最高优先级) |
因此,pci_walk_bridge 这个函数的作用就是,如果设备是桥,根据桥和下面的子设备error_detected 回调函数,综合判断设备要不要恢复,是走DISCONNECT
、NEED_RESET
还是CAN_RECOVER
。如果设备是RCEP,直接判断因该置位自己设备为哪种预备状态
3.5.1、pcie_do_recovery 整体总结--AER处理的核心部分
接下来重新回到 pcie_do_recovery 函数来看,这个函数的逻辑就比较清晰了,即:
(1)如果错误为 FATAL 错误,即设备A已经被标记成 pci_channel_io_frozen 状态了,这个时候先用深度优先算法,遍历该设备A和其下子设备,去检查是否满足reset条件,然后综合该设备和设备下挂子设备能否reset,给A这条线路置一个 PCI_ERS_RESULT_NEED_RESET 还是 PCI_ERS_RESULT_CAN_RECOVER 等的标识
(2)同时,如果错误为 FATAL 错误,会调用 reset_subordinates(bridge) != PCI_ERS_RESULT_RECOVERED,进而调用 aer_root_reset去重置该 bridge,并期待返回 PCI_ERS_RESULT_RECOVERED标志,否则报异常
(3)如果错误为NON - FATAL错误,仅仅标记该端口的PCIe端口status状态为 PCI_ERS_RESULT_NONE 或者是 PCI_ERS_RESULT_NO_AER_DRIVER,不进行端口或者总线的重置操作
(4)然后,根据端口在上面被标记的status状态,遍历调用 report_mmio_enabled 去恢复特定端口的MMIO功能。同时,更新端口A的status位
(5)然后,对执行MMIO恢复后的,status被标记为PCI_ERS_RESULT_NEED_RESET的端口,遍历调用 report_slot_reset ,进行槽位级别的复位
(6)最后,根据设置的是固件优先还是OS优先,去清除Device Status寄存器和Uncorrectable Error Status 寄存器的响应错误bit位
3.5.2、附加: aer_root_reset的函数分析--辅助理解pcie_do_recovery
static pci_ers_result_t aer_root_reset(struct pci_dev *dev) { // * - RCiEP需要找到关联的RCEC,其他设备直接找到根端口 if (type == PCI_EXP_TYPE_RC_END) root = dev->rcec; // RCiEP使用关联的RCEC else root = pcie_find_root_port(dev); // 其他设备查找根端口 // 如果平台保留AER控制权,RCiEP可能没有可见的RCEC,此时root可能为NULL,寄存器操作由固件负责 aer = root ? root->aer_cap : 0; // 获取AER能力位置 /* 阶段1: 禁用根端口错误中断 */ if ((host->native_aer || pcie_ports_native) && aer) { pci_read_config_dword(root, aer + PCI_ERR_ROOT_COMMAND, ®32); reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK; // 清除中断使能位 pci_write_config_dword(root, aer + PCI_ERR_ROOT_COMMAND, reg32); } /* 阶段2: 执行设备重置 */ if (type == PCI_EXP_TYPE_RC_EC || type == PCI_EXP_TYPE_RC_END) { /* RCEC/RCiEP使用功能级重置(FLR) */ rc = pcie_reset_flr(dev, PCI_RESET_DO_RESET); } else { /* 根端口/下游端口使用总线错误重置 */ rc = pci_bus_error_reset(dev); pci_info(dev, "%s端口链路已重置(%d)\n", pci_is_root_bus(dev->bus) ? "根" : "下游", rc); } /* 阶段3: 清理并恢复中断 */ if ((host->native_aer || pcie_ports_native) && aer) { /* 清除根错误状态寄存器 */ pci_read_config_dword(root, aer + PCI_ERR_ROOT_STATUS, ®32); pci_write_config_dword(root, aer + PCI_ERR_ROOT_STATUS, reg32); /* 重新启用根端口错误中断 */ pci_read_config_dword(root, aer + PCI_ERR_ROOT_COMMAND, ®32); reg32 |= ROOT_PORT_INTR_ON_MESG_MASK; // 设置中断使能位 pci_write_config_dword(root, aer + PCI_ERR_ROOT_COMMAND, reg32); } /* 返回结果: 成功恢复或需要断开 */ return rc ? PCI_ERS_RESULT_DISCONNECT : PCI_ERS_RESULT_RECOVERED; }
主要做下面几件事情:
(1)禁用根端口错误中断
#define ROOT_PORT_INTR_ON_MESG_MASK (PCI_ERR_ROOT_CMD_COR_EN| \ PCI_ERR_ROOT_CMD_NONFATAL_EN| \ PCI_ERR_ROOT_CMD_FATAL_EN)
(2)执行设备重置(FLR或链路重置),如果是RCEC/RCiEP,通过pcie_reset_flr()
执行功能级重置;如果是根端口或者下游端口,通过pci_bus_error_reset()
执行总线级重置
(3)清除根错误状态
(4)重新启用根端口错误中断
接着向下看调用 pci_bus_error_reset()
int pci_bus_error_reset(struct pci_dev *bridge) { /* 情况1:总线无槽位(如嵌入式设备),直接跳转总线复位 */ if (list_empty(&bus->slots)) goto bus_reset; /* 阶段1:检查所有槽位是否支持热复位 */ list_for_each_entry(slot, &bus->slots, list) if (pci_probe_reset_slot(slot)) // 探测槽位复位能力 goto bus_reset; // 任一槽位不支持则改用总线复位 /* 阶段2:执行实际槽位复位 */ list_for_each_entry(slot, &bus->slots, list) if (pci_slot_reset(slot, PCI_RESET_DO_RESET)) // 实际复位操作 goto bus_reset; // 任一槽位复位失败则改用总线复位 ... ... /* 降级处理路径:总线级复位 */ bus_reset: mutex_unlock(&pci_slot_mutex); return pci_bus_reset(bridge->subordinate, PCI_RESET_DO_RESET); }
采用渐进式复位策略:先尝试最小影响的槽位复位(pci_slot_reset
),失败时自动降级为总线复位(pci_bus_reset
)
pci_slot_reset --> pci_reset_hotplug_slot(slot->hotplug, probe); --> hotplug->ops->reset_slot(hotplug, probe); --> pciehp_reset_slot --> pci_bridge_secondary_bus_reset(ctrl->pcie->port);
(1)置位桥控制寄存器的BUS_RESET位,保持复位状态至少2ms(符合PCI规范v3.0 7.6.4.2)
(2)清除BUS_RESET位,等待1秒确保下游设备完成初始化
pci_bus_reset(struct pci_bus *bus, bool probe) --> pci_bridge_secondary_bus_reset(bus->self);
三、内核处理流程整体总结
(1)EP设备发生AER错误,通过error msg上报到root port, root port上报中断给CPU处理
(2)Correctable Errors处理流程:获取出错的设备和清状态,读取设备详细错误信息
(3)NON-FATAL Errors处理流程:获取出错的设备和清状态,读取设备详细错误信息,错误恢复处理
(4)FATAL Errors处理流程:大体类似non-fatal,只是错误恢复的时候有差异,FATAL Errors影响pcie link链路,因此会做链路的恢复