目錄
一、AER內核處理整體流程梳理
二、AER代碼重要部分梳理
1、AER初始化階段
2、中斷上半部 aer_irq
3、中斷下半部 aer_isr
3.1、aer_isr_one_error
3.2、find_source_device
3.3、aer_process_err_devices
3.4、handle_error_source
3.5、pcie_do_recovery 整體邏輯
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); ? ? ? // 提取可糾正錯誤IDe_info.severity = AER_CORRECTABLE; ? ? ? // 設置錯誤嚴重性為可糾正 ?// 檢查是否多個可糾正錯誤if (e_src->status & PCI_ERR_ROOT_MULTI_COR_RCV)e_info.multi_error_valid = 1; ? ? ? // 標記為多個錯誤elsee_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; ? ? ? ? // 致命錯誤elsee_info.severity = AER_NONFATAL; ? ? // 非致命錯誤 ?// 檢查是否多個不可糾正錯誤if (e_src->status & PCI_ERR_ROOT_MULTI_UNCOR_RCV)e_info.multi_error_valid = 1; ? ? ? // 標記為多個錯誤elsee_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; ?// 端口類設備自身作為恢復起點elsebridge = 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);elsecb(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;} elsenext = 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使用關聯的RCECelseroot = 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鏈路,因此會做鏈路的恢復