PCIE AER Linux 驱动详解

Abstract

Linux 内核的 AER 驱动提供一个干净、通用且独立于架构的解决方案。只要平台支持 PCI Express, AER(Advanced Error Reporting) 驱动程序应收集和管理所有发生的 PCI Express 错误并与 PCI Express 设备驱动程序合并执行错误恢复操作。

1. Introduction

外部设备可能出现错误,导致内核 panic, 系统可能崩溃,所以 IBM 工程师再内核中创建了一个框架去支持 PCI 错误恢复。然而,这模型缺乏支持平台独立的能力,个人开发者不易获取用于测试这些功能的设备。PCI Express 推出了标准的 AER。PCI Express AER 驱动程序旨在支持PCI Express AER。首先,任何支持的平台PCI Express 可以使用 PCI Express AER 驱动程序处理设备错误,并据此处理错误恢复。第二,因为很多设备都支持 PCIe, 所以对于个人开发者来说很容易获得设备并添加错误恢复代码到指定的设备驱动中。

2. PCIe Advanced Error Reportion Driver

2.1 PCIe AER Topology

要了解 PCI Express 高级错误报告驱动程序架构,首先要了解 PCI Express 端口拓扑的基础知识。图 1 说明两种类型的 PCI Express 端口设备:根端口和 Switch 端口。根端口发起一个来自 PCI Express RC 的链接。Switch Port,它的二级总线代表 Switch 内部的路由逻辑,称为 Switch Up stream Port。 桥接的 Switch 端口将内部路由总线切换到代表的总线下游的 PCI Express Link 称为 Switch Downstream Port。 每个 PCI Express 端口设备可以实施以支持多达四种不同的服务:本机热插拔 (HP)、电源管理事件 (PME)、高级错误报告 (AER)、虚拟通道 (VC)。

请添加图片描述
AER驱动开发基于服务 PCI Express Port Bus Driver 的驱动框架设计模型。 如图 2 所示,PCI Express AER 驱动程序用作根端口 AER 服务附加到 PCI Express 端口总线驱动程序的驱动程序。

请添加图片描述

2.2 PCIe AER Driver Architecture

PCI Express 错误信号可能发生在 PCI Express 链接本身或链接发起时的传输层。PCI Express 定义了 AER 功能,它用 PCI Express AER 扩展能力结构实现,允许 PCI Express 组件(代理)发送错误报告消息给根端口。 根端口是所有与其层次结构相关联的错误消息的主机接收器,解码错误将消息转换为错误类型和代理 ID,然后将这些记录到其 PCI Express AER 扩展功能中结构。如果检测到错误,根端口设备产生一个中断,这取决于报错报告消息在 Root Error Command 寄存器中是否启用。PCI Express AER 服务驱动程序被实施以服务由根端口产生的 AER 中断。图 3 说明了错误报告过程。

请添加图片描述

一旦加载了 PCI Express AER 服务驱动程序,它声明系统设备层次结构中的所有 AERrs 服务设备,如图 2 所示。对于每个 AERrs 服务设备,高级错误报告服务驱动程序配置其服务设备, 当检测到错误时产生中断。

发生错误时,PCI Express AER 驱动程序可以为此类基础设施提供三个基本功能:

  • 如果出现错误,则收集全面的发生的错误信息
  • 执行错误恢复指令
  • 向用户报告错误
2.2.1 PCIe Error Introduction

传统 PCI 设备提供简单的错误报告方法,PERR# 和 SERR#。PERR# 是奇偶校验错误,而SERR#是系统错误。所有非 PERR# 错误都是SERR#。PCI使用两条独立的信号线来表示PERR# 和 SERR#,它们是特定于平台芯片组的。至于如何将错误通知软件,这完全取决于特定的平台。

为了支持传统的错误处理,PCI Express 提供基线错误报告,它定义了基本的错误报告机制。 所有 PCI Express 设备必须实现此基线功能并且必须映射所需的 PCI Express 错误支持到 PCI 相关错误寄存器,包括启用错误报告和设置状态位。但是基线错误报告没有定义平台如何将错误通知系统软件。

PCI Express 错误包括两种类型,可纠正错误和无法纠正的错误。 可纠正的错误包括那些 PCI Express 协议可以恢复而不会丢失任何信息的错误情况。一个可纠正的错误,如果发生,可以由硬件纠正,无需任何软件干预。虽然硬件有能力纠正和减少可纠正的错误,但是可纠正的错误可能对系统有性能的影响。

PCI Express AER 提供更可靠的错误报告基础架构。 除了基线错误报告,PCI Express AER 定义了更细粒度的错误类型并提供日志功能。设备有标题日志寄存器以捕获检测到的错误相对应的 TLP 的头。

可纠正错误包括 receiver errors, bad TLP, bad DLLP, REPLAY_NUM rollover, and replay timer time-out。 当发生可纠正错误时,相应的高级可纠正错误状态中的位寄存器被设置。这些位由硬件自动设置,,由软件写“1”时清零。此外,通过高级可纠正错误掩码寄存器(具有类似的位图,如高级可纠正错误状态寄存器),一个特定的可纠正错误可以被掩盖而不是报告给根端口。虽然有掩码配置的位没有报错,相应高级可纠正错误状态寄存器中的位仍将被置位。

不可纠正的错误包括 Training Errors, Data Link Protocol Errors, Poisoned TLP Errors,Flow Control Protocol Errors, Completion Time-out Errors, Completer Abort Errors, Unexpected Completion Errors, Receiver Overflow Errors, Malformed TLPs, ECRC Errors, and Unsupported Request Errors。当一个不可纠正的发生错误时,对应位内高级不可纠正错误状态寄存器由硬件自动置位,由软件将“1”写入位时清零。高级错误处理允许软件在 Advanced Uncorrectable Error Severity register中选择每个错误的严重性。这使软件有机会根据相关的严重程度与给定的应用程序处理致命或非致命错误。软件可以使用高级不可纠正掩码寄存器来掩码特定错误。

2.2.2 PCI Express AER Driver Designed To Handle PCI Express Errors

在内核 2.6.18 之前,Linux 内核没有根端口 AER 服务驱动。通常,BIOS 提供基本的错误机制,但无法协调对应设备以获取更详细的错误信息和执行恢复操作。最后,AER 驱动已开发Linux 内核用于支持 PCI Express AER 启用。

2.2.2.1 AER Initialization Procedures

机器启动时,系统分配中断向量给每个 PCI Express 根端口。为了服务 PCI Express 根端口上的 PCI Express AER 中断,PCI Express AER 驱动程序注册其中断服务处理程序。一旦一个 PCI Express 根端口收到来自下游设备的错误报告, PCI Express 根端口发送一个CPU 的中断,Linux 内核将从中中断调用 PCI Express AER 中断服务处理程序。

大多数 AER 处理工作应在一个过程上下文完成。PCI Express AER 驱动程序为每个 PCI Express AER 根端口虚拟设备创建一个线程。取决于 AER 中断发生的位置在系统层次结构中,相应的线程将被调度。

大多数 BIOS 供应商提供非标准错误处理机制。为避免与 BIOS 冲突,同时处理 PCI Express 错误,PCI Express AER驱动程序必须请求 BIOS 以获得 PCI 的所有权,通过 ACPI _OSC 方法,在 PCI Express 规范和 ACPI 规范中描述的。如果BIOS 不支持 ACPI _OSC 方法,或者ACPI _OSC 方法返回错误,PCI Express AER 驱动程序的探测功能将失败(请参阅第 3 节如果 BIOS 供应商不支持解决方法ACPI _OSC 方法。

一旦 PCI Express AER 驱动程序接管,BIOS 必须停止其在 PCI Express 错误处理方面的活动。PCI Express AER 驱动程序然后配置 PCI Express 根端口和特定设备的 AER 能力寄存器以支持 PCI Express 原生 AER 的特定设备。

2.2.2.2 Handle PCI Express Correctable Errors

请添加图片描述

因为硬件可以纠正可纠正的错误无需任何软件干预,如果有发生时,在 PCI Express 根端口收到的消息变为错误类型和代理 ID, PCI Express AER 驱动程序首先解码此错误。 二、PCI Express AER驱动程序使用解码的错误信息来读取 PCI代理设备的 AER 能力,以获取有关错误的更多详细信息。 三、PCI Express AER驱动程序清除可纠正错误中的相应位PCI Express根端口的状态寄存器和代理设备。 图 4 说明了处理过程可纠正的错误。 最后但并非最不重要的细节关于错误将被格式化并输出到系统控制台如下图:

请添加图片描述

请求者 ID 是报告的设备的 ID错误。根据这些信息,管理员可以轻松找到坏设备。

2.2.2.3 Handle PCI Express Non-Fatal Errors

如果代理设备报告非致命错误,PCI Express AER 驱动程序使用与所述相同的机制,在第 2.2.2 节中获取有关来自代理设备的错误并输出错误信息到系统控制台。 图 5 说明了该过程处理非致命错误。
请添加图片描述

前两个步骤类似于处理可纠正的步骤错误。在步骤 2 中,如果错误与 TLP 相关, AER 驱动程序需要从代理检索数据包标头日志。

请添加图片描述

不像可纠正错误,非致命错误可能导致一些传输失败。为了帮助代理设备驱动程序重试任何失败的事务,PCI Express AER驱动程序必须执行非致命错误恢复程序,这取决于发生非致命错误的位置在系统层次结构中。如图 6 所示,例如,有两个 PCI Express Switch。如果端点设备 E2 报告一个非致命错误,PCI Express AER 驱动程序将尝试仅在此设备上执行错误恢复。其他设备不会参与此错误恢复过程。如果下游端口 Switch 1 的 P1 报告一个非致命错误,PCI Express AER 驱动程序将在端口 P1 下所有设备上执行错误恢复程序,包括Switch 2 的所有端口,端点 E1 和 E2。

请添加图片描述

要参与错误恢复过程,特定设备驱动程序需要按照描述实现错误回调,在第 4.1 节中描述。

当发生不可纠正的非致命错误时,AER 错误恢复程序首先调用 error_detected 遍历所有相关驱动的例行程序,以深度优先顺序通知设备。在回调error_detected中,驱动程序不应该操作设备,即不要在设备上执行任何 I/O设备。大多数情况下,error_detected 可能会取消所有待处理的请求或将请求放入队列。

如果来自所有相关的 error_detected 例程返回值是 PCI_ERS_RESULT_CAN_RECOVER,AER 恢复过程调用所有相关驱动的恢复回调,在恢复功能中,
驱动程序可以恢复对设备的操作。

如果 error_detected 回调返回 PCI_ERS_RESULT_NEED_RESET,恢复过程将调用相关驱动程序的所有 slot_reset 回调。如果所有 slot_reset 函数都返回 PCI_ERS_RESULT_CAN_RECOVER,恢复回调将被调用到完成恢复。目前,一些设备驱动程序提供 err_handler 回调。例如,英特尔的E100和E1000网卡驱动和IBM的 POWERRAID 驱动程序。

PCI Express AER 驱动程序输出一些信息关于非致命错误恢复步骤和结果。 以下是一个例子:

请添加图片描述

2.2.2.4 Handle PCI Express Fatal Errors

处理致命错误时,PCI Express AER 驱动程序还从上报者用2.2.2.2 和 2.2.2.3章节中描述的方式收集详细错误信息。下面是一个非致命的例子错误输出到系统控制台:

请添加图片描述

当执行错误恢复程序时,致命与非致命之间的主要区别是链接是否复位。如果从所有相关的 error_detected 例程的返回值是PCI_ERS_RESULT_CAN_RECOVER,AER 恢复程序根据代理设备是否是桥复位 PCI Express 链路。图 7 说明了一个示例。

请添加图片描述

在图 7 中,如果根端口 P0(一种桥)报告自身发生致命错误,PCI Express AER 驱动程序选择复位根端口 P0 和上游链路端口 P1。 如果端点设备 E1 报告致命错误,PCI Express AER 驱动程序选择复位E1的上行链路,即P2和E1之间的链路。

复位由端口执行。如果代理是一个端口,端口将执行复位。如果代理是端点设备,例如图7中的E1,上游的端口E1 的链路,即端口 P2 将执行复位。

复位方法取决于端口类型。至于根端口和下行端口,PCI Express 规范定义了一种复位其下游链接的方法。在图 7 中,如果端口 P0、P2、P3 和端点 E1 报告致命错误,PCI Express 规范中定义的方法将会被使用。PCI Express AER 驱动程序实现标准方法作为默认重置功能。

没有标准的方法来复位上行端口下的下游链接,因为不同的 Switch 可能会实施不同的复位方法。为了方便链路复位方法,PCI Express AER 驱动程序在数据中添加 reset_link,后续版本改为 slot_reset, 一个新的函数指针结构 pcie_port_service_driver。


struct pcie_port_service_driver {
	const char *name;
	int (*probe)(struct pcie_device *dev);
	void (*remove)(struct pcie_device *dev);
	int (*suspend)(struct pcie_device *dev);
	int (*resume_noirq)(struct pcie_device *dev);
	int (*resume)(struct pcie_device *dev);
	int (*runtime_suspend)(struct pcie_device *dev);
	int (*runtime_resume)(struct pcie_device *dev);

	int (*slot_reset)(struct pcie_device *dev);

	int port_type;  /* Type of the port this driver can handle */
	u32 service;    /* Port service this device represents */

	struct device_driver driver;
};

如果端口使用指定供应商的方法来复位链路,它的 AER 端口服务驱动程序必须提供一个 slot_reset 功能。 如果根端口驱动程序或下游端口服务驱动程序不提供 slot_reset 函数,将调用默认的 slot_reset 函数。 如果上游端口服务驱动程序没有实现 slot_reset 函数,错误恢复将失败。

下面是 AER 驱动再致命错误恢复时系统终端输出打印例子:

请添加图片描述

2.3 Including PCI Express Advanced Error Reporting Driver Into the Kernel

PCI Express AER Root 驱动程序是 Root Port 服务附加到 PCI Express 端口总线驱动程序的驱动程序。它的服务必须注册到 PCI Express 端口总线驱动程序和用户需要把 PCI Express 的端口总线驱动程序添加到内核中。 一旦内核包括配置选项 CONFIG_PCIEPORTBUS,PCI Express AER Root 驱动程序默认情况下自动作为内核驱动程序包含进去(CONFIG_PCIEAER = Y)。

3. Driver Analyse


static struct pcie_port_service_driver aerdriver = {
	.name		= "aer",
	.port_type	= PCIE_ANY_PORT,
	.service	= PCIE_PORT_SERVICE_AER,
	// 匹配调用 probe 函数
	.probe		= aer_probe,
	.remove		= aer_remove,
};

/**
 * pcie_aer_init - register AER root service driver
 *
 * Invoked when AER root service driver is loaded.
 */
int __init pcie_aer_init(void)
{
	if (!pci_aer_available())
		return -ENXIO;
	// aerdriver 作为一个 port service 注册进总线中
	return pcie_port_service_register(&aerdriver);
}

// 初始化函数
static int aer_probe(struct pcie_device *dev)
{
	int status;
	struct aer_rpc *rpc;
	struct device *device = &dev->device;
	struct pci_dev *port = dev->port;

	/* Limit to Root Ports or Root Complex Event Collectors */
	// 限制,只对根端口和 RCEC 进行配置
	if ((pci_pcie_type(port) != PCI_EXP_TYPE_RC_EC) &&
	    (pci_pcie_type(port) != PCI_EXP_TYPE_ROOT_PORT))
		return -ENODEV;

	rpc = devm_kzalloc(device, sizeof(struct aer_rpc), GFP_KERNEL);
	if (!rpc)
		return -ENOMEM;

	rpc->rpd = port;
	INIT_KFIFO(rpc->aer_fifo);
	set_service_data(dev, rpc);

	// 申请终端服务程序, aer_irq 中断处理程序
	// aer_isr 中断处理线程
	status = devm_request_threaded_irq(device, dev->irq, aer_irq, aer_isr,
					   IRQF_SHARED, "aerdrv", dev);
	if (status) {
		pci_err(port, "request AER IRQ %d failed\n", dev->irq);
		return status;
	}
	// 使能根端口
	aer_enable_rootport(rpc);
	pci_info(port, "enabled with IRQ %d\n", dev->irq);
	return 0;
}

AER 能力结构体布局:

请添加图片描述

下面函数会用到:

请添加图片描述

请添加图片描述


#define PCI_ERR_ROOT_STATUS	    0x30
#define PCI_ERR_ROOT_ERR_SRC	0x34	/* Error Source Identification */

#define AER_ERR_STATUS_MASK		(PCI_ERR_ROOT_UNCOR_RCV |	\
					PCI_ERR_ROOT_COR_RCV |		\
					PCI_ERR_ROOT_MULTI_COR_RCV |	\
					PCI_ERR_ROOT_MULTI_UNCOR_RCV)

/**
 * aer_irq - Root Port's ISR
 * @irq: IRQ assigned to Root Port
 * @context: pointer to Root Port data structure
 *
 * Invoked when Root Port detects AER messages.
 */

// 错误上报,中断处理程序
static irqreturn_t aer_irq(int irq, void *context)
{
	struct pcie_device *pdev = (struct pcie_device *)context;
	struct aer_rpc *rpc = get_service_data(pdev);
	struct pci_dev *rp = rpc->rpd;
	int aer = rp->aer_cap;
	struct aer_err_source e_src = {};
	// Root Error Status Register 报告错误消息状态(ERR_COR, ERR_NONFATAL, and ERR_FATAL)
	pci_read_config_dword(rp, aer + PCI_ERR_ROOT_STATUS, &e_src.status);
	// 检查是否有 UNCOR COR MULTI_COR MULTI_UNCOR 置位
	if (!(e_src.status & AER_ERR_STATUS_MASK))
		return IRQ_NONE;
    // 读取 Requester 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);
	// 添加到缓冲区去处理
	if (!kfifo_put(&rpc->aer_fifo, e_src))
		return IRQ_HANDLED;

	return IRQ_WAKE_THREAD;
}



/**
 * aer_isr - consume errors detected by root port
 * @irq: IRQ assigned to Root Port
 * @context: pointer to Root Port data structure
 *
 * Invoked, as DPC, when root port records new detected error
 */
// 处理一次错误
static irqreturn_t aer_isr(int irq, void *context)
{
	struct pcie_device *dev = (struct pcie_device *)context;
	struct aer_rpc *rpc = get_service_data(dev);
	struct aer_err_source e_src;

	// 检查缓冲区是否为空
	if (kfifo_is_empty(&rpc->aer_fifo))
		return IRQ_NONE;
	// 循环处理错误
	while (kfifo_get(&rpc->aer_fifo, &e_src))
		aer_isr_one_error(rpc, &e_src);
	return IRQ_HANDLED;
}

部分错误状态寄存器位:

请添加图片描述


/**
 * aer_isr_one_error - consume an error detected by root port
 * @rpc: pointer to the root port which holds an error
 * @e_src: pointer to an error source
 */
// 处理一次错误
static void aer_isr_one_error(struct aer_rpc *rpc,
		struct aer_err_source *e_src)
{
	struct pci_dev *pdev = rpc->rpd;
	struct aer_err_info e_info;
	// 根据错误状态,进行错误计数处理
	pci_rootport_aer_stats_incr(pdev, e_src);

	/*
	 * There is a possibility that both correctable error and
	 * uncorrectable error being logged. Report correctable error first.
	 */
	// 如果是可纠正错误
	if (e_src->status & PCI_ERR_ROOT_COR_RCV) {
		// 获取出错上报设备的ID, 设置严重性 AER_CORRECTABLE
		e_info.id = ERR_COR_ID(e_src->id);
		e_info.severity = AER_CORRECTABLE;
		// 如果收到一个可纠正错误消息,但是 ERR_COR 寄存器已经置位,说明收到多个可纠正错误消息,进行记录
		if (e_src->status & PCI_ERR_ROOT_MULTI_COR_RCV)
			e_info.multi_error_valid = 1;
		else
			e_info.multi_error_valid = 0;
		// AER 打印
		// error received: .....
		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, 设置严重性 AER_CORRECTABLE
		if (e_src->status & PCI_ERR_ROOT_FATAL_RCV)
			e_info.severity = AER_FATAL;
		else
			e_info.severity = AER_NONFATAL;
		// 如果收到一个可纠正错误消息,但是 ERR_COR 寄存器已经置位,说明收到多个可纠正错误消息,进行记录
		if (e_src->status & PCI_ERR_ROOT_MULTI_UNCOR_RCV)
			e_info.multi_error_valid = 1;
		else
			e_info.multi_error_valid = 0;
		// AER 打印
		// error received: .....
		aer_print_port_info(pdev, &e_info);
		// 寻找上报错误的设备,如果找到进行处理
		if (find_source_device(pdev, &e_info))
			aer_process_err_devices(&e_info);
	}
}


static void pci_rootport_aer_stats_incr(struct pci_dev *pdev,
				 struct aer_err_source *e_src)
{
	struct aer_stats *aer_stats = pdev->aer_stats;

	if (!aer_stats)
		return;
	// 如果可纠正错误状态位,计数加1
	if (e_src->status & PCI_ERR_ROOT_COR_RCV)
		aer_stats->rootport_total_cor_errs++;
	// 如果不可纠正错误状态位
	if (e_src->status & PCI_ERR_ROOT_UNCOR_RCV) {
		// 致命与非致命错误计数处理
		if (e_src->status & PCI_ERR_ROOT_FATAL_RCV)
			aer_stats->rootport_total_fatal_errs++;
		else
			aer_stats->rootport_total_nonfatal_errs++;
	}
}



static void aer_print_port_info(struct pci_dev *dev, struct aer_err_info *info)
{
	u8 bus = info->id >> 8;
	u8 devfn = info->id & 0xff;

	pci_info(dev, "%s%s error received: %04x:%02x:%02x.%d\n",
		 info->multi_error_valid ? "Multiple " : "",
		 aer_error_severity_string[info->severity],
		 pci_domain_nr(dev->bus), bus, PCI_SLOT(devfn),
		 PCI_FUNC(devfn));
}

------------------------------    find_source_device  Start ------------------------------

/**
 * find_source_device - search through device hierarchy for source device
 * @parent: pointer to Root Port pci_dev data structure
 * @e_info: including detailed error information such like id
 *
 * Return true if found.
 *
 * Invoked by DPC when error is detected at the Root Port.
 * Caller of this function must set id, severity, and multi_error_valid of
 * struct aer_err_info pointed by @e_info properly.  This function must fill
 * e_info->error_dev_num and e_info->dev[], based on the given information.
 */
// 根据层次架构寻找源设备,上报错误的设备
static bool find_source_device(struct pci_dev *parent,
		struct aer_err_info *e_info)
{
	struct pci_dev *dev = parent;
	int result;

	/* Must reset in this function */
	e_info->error_dev_num = 0;

	/* Is Root Port an agent that sends error message? */
	result = find_device_iter(dev, e_info);
	if (result)
		return true;
	// 如果没找到,进行遍历
	// RC 下面的设备与其他桥下面的设备区分处理
	// 找到设备调用回调  find_device_iter
	if (pci_pcie_type(parent) == PCI_EXP_TYPE_RC_EC)
		pcie_walk_rcec(parent, find_device_iter, e_info);
	else
		pci_walk_bus(parent->subordinate, find_device_iter, e_info);
	// error_dev_num 记录找到的设备的数量
	if (!e_info->error_dev_num) {
		pci_info(parent, "can't find device of ID%04x\n", e_info->id);
		return false;
	}
	// error_dev_num 不为0 ,则找到设备,返回 true
	return true;
}


static int find_device_iter(struct pci_dev *dev, void *data)
{
	struct aer_err_info *e_info = (struct aer_err_info *)data;
	
	if (is_error_source(dev, e_info)) {
		// 如果设备时错误源
		/* List this device */
		// 添加设备到数组中,进行记录
		if (add_error_device(e_info, dev)) {
			/* We cannot handle more... Stop iteration */
			/* TODO: Should print error message here? */
			return 1;
		}

		/* If there is only a single error, stop iteration */
		if (!e_info->multi_error_valid)
			return 1;
	}
	return 0;
}


/**
 * is_error_source - check whether the device is source of reported error
 * @dev: pointer to pci_dev to be checked
 * @e_info: pointer to reported error info
 */
static bool is_error_source(struct pci_dev *dev, struct aer_err_info *e_info)
{
	int aer = dev->aer_cap;
	u32 status, mask;
	u16 reg16;

	/*
	 * When bus id is equal to 0, it might be a bad id
	 * reported by root port.
	 */
	// ID 是 bus Number \ device Number \ Function Number 结合
	if ((PCI_BUS_NUM(e_info->id) != 0) &&
	    !(dev->bus->bus_flags & PCI_BUS_FLAGS_NO_AERSID)) {
		/* Device ID match? */
		// 匹配
		if (e_info->id == ((dev->bus->number << 8) | dev->devfn))
			return true;

		/* Continue id comparing if there is no multiple error */
		if (!e_info->multi_error_valid)
			return false;
	}

	/*
	 * When either
	 *      1) bus id is equal to 0. Some ports might lose the bus
	 *              id of error source id;
	 *      2) bus flag PCI_BUS_FLAGS_NO_AERSID is set
	 *      3) There are multiple errors and prior ID comparing fails;
	 * We check AER status registers to find possible reporter.
	 */
	if (atomic_read(&dev->enable_cnt) == 0)
		return false;

	/* Check if AER is enabled */
	pcie_capability_read_word(dev, PCI_EXP_DEVCTL, &reg16);
	if (!(reg16 & PCI_EXP_AER_FLAGS))
		return false;

	if (!aer)
		return false;

	/* Check if error is recorded */
	// 检查错误是否记录,如果 mask 置位了,不会记录
	if (e_info->severity == AER_CORRECTABLE) {
		pci_read_config_dword(dev, aer + PCI_ERR_COR_STATUS, &status);
		pci_read_config_dword(dev, aer + PCI_ERR_COR_MASK, &mask);
	} else {
		pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_STATUS, &status);
		pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_MASK, &mask);
	}
	if (status & ~mask)
		return true;

	return false;
}


/**
 * add_error_device - list device to be handled
 * @e_info: pointer to error info
 * @dev: pointer to pci_dev to be added
 */
static int add_error_device(struct aer_err_info *e_info, struct pci_dev *dev)
{
	// 记录设备到数组中
	if (e_info->error_dev_num < AER_MAX_MULTI_ERR_DEVICES) {
		e_info->dev[e_info->error_dev_num] = pci_dev_get(dev);
		e_info->error_dev_num++;
		return 0;
	}
	return -ENOSPC;
}


/**
 * pcie_walk_rcec - Walk RCiEP devices associating with RCEC and call callback.
 * @rcec:	RCEC whose RCiEP devices should be walked
 * @cb:		Callback to be called for each RCiEP device found
 * @userdata:	Arbitrary pointer to be passed to callback
 *
 * Walk the given RCEC. Call the callback on each RCiEP found.
 *
 * If @cb returns anything other than 0, break out.
 */
// linux-5.18.8\drivers\pci\pcie\rcec.c
// 遍历有 RCEC的 RCiEP 设备并且调用回调函数 cb
void pcie_walk_rcec(struct pci_dev *rcec, int (*cb)(struct pci_dev *, void *),
		    void *userdata)
{
	struct walk_rcec_data rcec_data;

	if (!rcec->rcec_ea)
		return;

	rcec_data.rcec = rcec;
	rcec_data.user_callback = cb;
	rcec_data.user_data = userdata;

	walk_rcec(walk_rcec_helper, &rcec_data);
}


------------------------------    find_source_device  End ------------------------------

aer_get_device_error_info() 使用的两个寄存器:

请添加图片描述

请添加图片描述

请添加图片描述

请添加图片描述

请添加图片描述

请添加图片描述


static inline void aer_process_err_devices(struct aer_err_info *e_info)
{
	int i;

	/* Report all before handle them, not to lost records by reset etc. */
	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))
			// 先对错误进行打印
			// PCIe Bus Error: severity: .....
			//   device [%04x:%04x] error status/mask= ....
			// ...
			//   TLP Header: ....
			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);
	}
}



/**
 * aer_get_device_error_info - read error status from dev and store it to info
 * @dev: pointer to the device expected to have a error record
 * @info: pointer to structure to store the error record
 *
 * Return 1 on success, 0 on error.
 *
 * Note that @info is reused among all error devices. Clear fields properly.
 */
// 从设备读取错误状态并且存储到 info

#define PCI_ERR_UNCOR_STATUS	0x04	/* Uncorrectable Error Status */
#define PCI_ERR_UNCOR_MASK	0x08	/* Uncorrectable Error Mask */
#define PCI_ERR_COR_STATUS	0x10	/* Correctable Error Status */
#define PCI_ERR_COR_MASK	0x14	/* Correctable Error Mask */
#define PCI_ERR_CAP		0x18	/* Advanced Error Capabilities & Ctrl*/

#define AER_LOG_TLP_MASKS		(PCI_ERR_UNC_POISON_TLP|	\
					PCI_ERR_UNC_ECRC|		\
					PCI_ERR_UNC_UNSUP|		\
					PCI_ERR_UNC_COMP_ABORT|		\
					PCI_ERR_UNC_UNX_COMP|		\
					PCI_ERR_UNC_MALF_TLP)
					
int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info)
{
	int type = pci_pcie_type(dev);
	int aer = dev->aer_cap;
	int temp;

	/* Must reset in this function */
	info->status = 0;
	info->tlp_header_valid = 0;

	/* The device might not support AER */
	if (!aer)
		return 0;

	if (info->severity == AER_CORRECTABLE) {
		// 如果是可纠正错误, 读取 AER Cap Reg 寄存器0x10 的Correctable Error Status Register 以及 Mask Reg
		pci_read_config_dword(dev, aer + PCI_ERR_COR_STATUS,
			&info->status);
		pci_read_config_dword(dev, aer + PCI_ERR_COR_MASK,
			&info->mask);
		// Mask 置位的部分不报错误消息,但是 status 仍然置位
		// 检查 Mask 不为1的位,有错的情况,没有错就返回0
		// 仍然不懂,自己举个例子就理解了, status = 1, mask = 1 情况是没有错误消息的
		if (!(info->status & ~info->mask))
			return 0;
	} else if (type == PCI_EXP_TYPE_ROOT_PORT ||
		   type == PCI_EXP_TYPE_RC_EC ||
		   type == PCI_EXP_TYPE_DOWNSTREAM ||
		   info->severity == AER_NONFATAL) {
		// 非致命错误
		/* Link is still healthy for IO reads */
		// 读 Status Mask 寄存器的值
		pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_STATUS,
			&info->status);
		pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_MASK,
			&info->mask);
		// 同样,检查 Mask 没有置1的位,是否有错
		if (!(info->status & ~info->mask))
			return 0;

		// 如果有错误
		/* Get First Error Pointer */
		// 读 Advanced Error Capabilities and Control Register 的值
		pci_read_config_dword(dev, aer + PCI_ERR_CAP, &temp);
		// Reg 的 First Error Pointer 位(4:0)
		// 是在不可纠正错误状态寄存器中报告的标识第一个错误的位位置的字段
		// the First Error Pointer (when valid) points to the oldest uncorrectable error that is recorded
		info->first_error = PCI_ERR_CAP_FEP(temp);

		if (info->status & AER_LOG_TLP_MASKS) {
			// 如果错误包括  AER_LOG_TLP_MASKS 中定义的这些
			info->tlp_header_valid = 1;
			// The Header Log Register contains the header for the TLP corresponding to a detected error
			// 此寄存器包含与检测到的错误相关的 TLP 头,详情参考 PCIE Spec
			pci_read_config_dword(dev,
				aer + PCI_ERR_HEADER_LOG, &info->tlp.dw0);
			pci_read_config_dword(dev,
				aer + PCI_ERR_HEADER_LOG + 4, &info->tlp.dw1);
			pci_read_config_dword(dev,
				aer + PCI_ERR_HEADER_LOG + 8, &info->tlp.dw2);
			pci_read_config_dword(dev,
				aer + PCI_ERR_HEADER_LOG + 12, &info->tlp.dw3);
		}
	}

	return 1;
}

------------------------------    aer_print_error  Start ------------------------------

static void __aer_print_error(struct pci_dev *dev,
			      struct aer_err_info *info)
{
	const char **strings;
	unsigned long status = info->status & ~info->mask;
	const char *level, *errmsg;
	int i;

	if (info->severity == AER_CORRECTABLE) {
		strings = aer_correctable_error_string;
		level = KERN_WARNING;
	} else {
		strings = aer_uncorrectable_error_string;
		level = KERN_ERR;
	}

	for_each_set_bit(i, &status, 32) {
		errmsg = strings[i];
		if (!errmsg)
			errmsg = "Unknown Error Bit";

		pci_printk(level, dev, "   [%2d] %-22s%s\n", i, errmsg,
				info->first_error == i ? " (First)" : "");
	}
	pci_dev_aer_stats_incr(dev, info);
}


static void __print_tlp_header(struct pci_dev *dev,
			       struct aer_header_log_regs *t)
{
	pci_err(dev, "  TLP Header: %08x %08x %08x %08x\n",
		t->dw0, t->dw1, t->dw2, t->dw3);
}

// 对错误进行打印
// PCIe Bus Error: severity: .....
//   device [%04x:%04x] error status/mask= ....
// ...
//   TLP Header: ....
void aer_print_error(struct pci_dev *dev, struct aer_err_info *info)
{
	int layer, agent;
	int id = ((dev->bus->number << 8) | dev->devfn);
	const char *level;

	if (!info->status) {
		pci_err(dev, "PCIe Bus Error: severity=%s, type=Inaccessible, (Unregistered Agent ID)\n",
			aer_error_severity_string[info->severity]);
		goto out;
	}

	layer = AER_GET_LAYER_ERROR(info->severity, info->status);
	agent = AER_GET_AGENT(info->severity, info->status);

	level = (info->severity == AER_CORRECTABLE) ? KERN_WARNING : KERN_ERR;

	pci_printk(level, dev, "PCIe Bus Error: severity=%s, type=%s, (%s)\n",
		   aer_error_severity_string[info->severity],
		   aer_error_layer[layer], aer_agent_string[agent]);

	pci_printk(level, dev, "  device [%04x:%04x] error status/mask=%08x/%08x\n",
		   dev->vendor, dev->device, info->status, info->mask);

	__aer_print_error(dev, info);

	if (info->tlp_header_valid)
		__print_tlp_header(dev, &info->tlp);

out:
	if (info->id && info->error_dev_num > 1 && info->id == id)
		pci_err(dev, "  Error of this Agent is reported first\n");

	trace_aer_event(dev_name(&dev->dev), (info->status & ~info->mask),
			info->severity, info->tlp_header_valid, &info->tlp);
}


------------------------------    aer_print_error  End ------------------------------

// linux-5.18.8\drivers\pci\pcie\err.c

/**
 * handle_error_source - handle logging error into an event log
 * @dev: pointer to pci_dev data structure of error source device
 * @info: comprehensive error information
 *
 * Invoked when an error being detected by Root Port.
 */
// 进行错误处理
static void handle_error_source(struct pci_dev *dev, struct aer_err_info *info)
{
	int aer = dev->aer_cap;

	if (info->severity == AER_CORRECTABLE) {
		// 如果是可纠正错误
		/*
		 * Correctable error does not need software intervention.
		 * No need to go through error recovery process.
		 */
		if (aer)
			pci_write_config_dword(dev, aer + PCI_ERR_COR_STATUS,
					info->status);
		if (pcie_aer_is_native(dev))
			// 如果是本地的,清除设备状态
			pcie_clear_device_status(dev);
	} else if (info->severity == AER_NONFATAL)
		// 如果是非致命错误 pci_channel_io_normal
		pcie_do_recovery(dev, pci_channel_io_normal, aer_root_reset);
	else if (info->severity == AER_FATAL)
		// 如果是致命错误 pci_channel_io_frozen
		pcie_do_recovery(dev, pci_channel_io_frozen, aer_root_reset);
	pci_dev_put(dev);
}


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))
{
	int type = pci_pcie_type(dev);
	struct pci_dev *bridge;
	pci_ers_result_t status = PCI_ERS_RESULT_CAN_RECOVER;
	struct pci_host_bridge *host = pci_find_host_bridge(dev->bus);

	/*
	 * If the error was detected by a Root Port, Downstream Port, RCEC,
	 * or RCiEP, recovery runs on the device itself.  For Ports, that
	 * also includes any subordinate devices.
	 *
	 * If it was detected by another device (Endpoint, etc), recovery
	 * runs on the device and anything else under the same Port, i.e.,
	 * everything under "bridge".
	 */
	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);

	pci_dbg(bridge, "broadcast error_detected message\n");
	if (state == pci_channel_io_frozen) {
		// 遍历桥或设备,回调 report_frozen_detected
		// 如果是致命错误, 调用此
		pci_walk_bridge(bridge, report_frozen_detected, &status);
		if (reset_subordinates(bridge) != PCI_ERS_RESULT_RECOVERED) {
			pci_warn(bridge, "subordinate device reset failed\n");
			goto failed;
		}
	} else {
		// 遍历桥或设备,回调 report_normal_detected
		// 如果是非致命错误, 调用此
		pci_walk_bridge(bridge, report_normal_detected, &status);
	}

	if (status == PCI_ERS_RESULT_CAN_RECOVER) {
		status = PCI_ERS_RESULT_RECOVERED;
		pci_dbg(bridge, "broadcast mmio_enabled message\n");
		pci_walk_bridge(bridge, report_mmio_enabled, &status);
	}

	if (status == PCI_ERS_RESULT_NEED_RESET) {
		// 需要调用复位函数
		/*
		 * TODO: Should call platform-specific
		 * functions to reset slot before calling
		 * drivers' slot_reset callbacks?
		 */
		status = PCI_ERS_RESULT_RECOVERED;
		pci_dbg(bridge, "broadcast slot_reset message\n");
		pci_walk_bridge(bridge, report_slot_reset, &status);
	}

	if (status != PCI_ERS_RESULT_RECOVERED)
		goto failed;

	pci_dbg(bridge, "broadcast resume message\n");
	pci_walk_bridge(bridge, report_resume, &status);

	/*
	 * If we have native control of AER, clear error status in the device
	 * that detected the error.  If the platform retained control of AER,
	 * it is responsible for clearing this status.  In that case, the
	 * signaling device may not even be visible to the OS.
	 */
	if (host->native_aer || pcie_ports_native) {
		pcie_clear_device_status(dev);
		pci_aer_clear_nonfatal_status(dev);
	}
	pci_info(bridge, "device recovery successful\n");
	return status;

failed:
	pci_uevent_ers(bridge, PCI_ERS_RESULT_DISCONNECT);

	/* TODO: Should kernel panic here? */
	pci_info(bridge, "device recovery failed\n");

	return status;
}

static int report_frozen_detected(struct pci_dev *dev, void *data)
{
	return report_error_detected(dev, pci_channel_io_frozen, data);
}

static int report_normal_detected(struct pci_dev *dev, void *data)
{
	return report_error_detected(dev, pci_channel_io_normal, data);
}

static int report_error_detected(struct pci_dev *dev,
				 pci_channel_state_t state,
				 enum pci_ers_result *result)
{
	struct pci_driver *pdrv;
	pci_ers_result_t vote;
	const struct pci_error_handlers *err_handler;

	device_lock(&dev->dev);
	pdrv = dev->driver;
	if (!pci_dev_set_io_state(dev, state) ||
		!pdrv ||
		!pdrv->err_handler ||
		!pdrv->err_handler->error_detected) {
		/*
		 * If any device in the subtree does not have an error_detected
		 * callback, PCI_ERS_RESULT_NO_AER_DRIVER prevents subsequent
		 * error callbacks of "any" device in the subtree, and will
		 * exit in the disconnected error state.
		 */
		if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) {
			vote = PCI_ERS_RESULT_NO_AER_DRIVER;
			pci_info(dev, "can't recover (no error_detected callback)\n");
		} else {
			vote = PCI_ERS_RESULT_NONE;
		}
	} else {
		err_handler = pdrv->err_handler;
		vote = err_handler->error_detected(dev, state);
	}
	pci_uevent_ers(dev, vote);
	*result = merge_result(*result, vote);
	device_unlock(&dev->dev);
	return 0;
}

其他函数解析



int pci_enable_pcie_error_reporting(struct pci_dev *dev)
{
	int rc;

	if (!pcie_aer_is_native(dev))
		return -EIO;
	// 使能寄存器
	rc = pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_AER_FLAGS);
	return pcibios_err_to_errno(rc);
}
EXPORT_SYMBOL_GPL(pci_enable_pcie_error_reporting);

int pci_disable_pcie_error_reporting(struct pci_dev *dev)
{
	int rc;

	if (!pcie_aer_is_native(dev))
		return -EIO;
	// 清除寄存器位
	rc = pcie_capability_clear_word(dev, PCI_EXP_DEVCTL, PCI_EXP_AER_FLAGS);
	return pcibios_err_to_errno(rc);
}
EXPORT_SYMBOL_GPL(pci_disable_pcie_error_reporting);


int pci_aer_clear_nonfatal_status(struct pci_dev *dev)
{
	int aer = dev->aer_cap;
	u32 status, sev;

	if (!pcie_aer_is_native(dev))
		return -EIO;

	/* Clear status bits for ERR_NONFATAL errors only */
	pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_STATUS, &status);
	pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_SEVER, &sev);
	status &= ~sev;
	// 清除,写 1 即可
	if (status)
		pci_write_config_dword(dev, aer + PCI_ERR_UNCOR_STATUS, status);

	return 0;
}
EXPORT_SYMBOL_GPL(pci_aer_clear_nonfatal_status);

void pci_aer_clear_fatal_status(struct pci_dev *dev)
{
	int aer = dev->aer_cap;
	u32 status, sev;

	if (!pcie_aer_is_native(dev))
		return;

	/* Clear status bits for ERR_FATAL errors only */
	pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_STATUS, &status);
	pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_SEVER, &sev);
	status &= sev;
	// 清除,写 1 即可
	if (status)
		pci_write_config_dword(dev, aer + PCI_ERR_UNCOR_STATUS, status);
}

/**
 * pci_aer_raw_clear_status - Clear AER error registers.
 * @dev: the PCI device
 *
 * Clearing AER error status registers unconditionally, regardless of
 * whether they're owned by firmware or the OS.
 *
 * Returns 0 on success, or negative on failure.
 */
int pci_aer_raw_clear_status(struct pci_dev *dev)
{
	int aer = dev->aer_cap;
	u32 status;
	int port_type;

	if (!aer)
		return -EIO;

	port_type = pci_pcie_type(dev);
	if (port_type == PCI_EXP_TYPE_ROOT_PORT ||
	    port_type == PCI_EXP_TYPE_RC_EC) {
		pci_read_config_dword(dev, aer + PCI_ERR_ROOT_STATUS, &status);
		pci_write_config_dword(dev, aer + PCI_ERR_ROOT_STATUS, status);
	}
	// 清除,写 1 即可
	pci_read_config_dword(dev, aer + PCI_ERR_COR_STATUS, &status);
	pci_write_config_dword(dev, aer + PCI_ERR_COR_STATUS, status);

	pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_STATUS, &status);
	pci_write_config_dword(dev, aer + PCI_ERR_UNCOR_STATUS, status);

	return 0;
}

int pci_aer_clear_status(struct pci_dev *dev)
{
	if (!pcie_aer_is_native(dev))
		return -EIO;

	return pci_aer_raw_clear_status(dev);
}

void pci_save_aer_state(struct pci_dev *dev)
{
	int aer = dev->aer_cap;
	struct pci_cap_saved_state *save_state;
	u32 *cap;

	if (!aer)
		return;

	save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_ERR);
	if (!save_state)
		return;

	cap = &save_state->cap.data[0];
	pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_MASK, cap++);
	pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_SEVER, cap++);
	pci_read_config_dword(dev, aer + PCI_ERR_COR_MASK, cap++);
	pci_read_config_dword(dev, aer + PCI_ERR_CAP, cap++);
	if (pcie_cap_has_rtctl(dev))
		pci_read_config_dword(dev, aer + PCI_ERR_ROOT_COMMAND, cap++);
}

void pci_restore_aer_state(struct pci_dev *dev)
{
	int aer = dev->aer_cap;
	struct pci_cap_saved_state *save_state;
	u32 *cap;

	if (!aer)
		return;

	save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_ERR);
	if (!save_state)
		return;

	cap = &save_state->cap.data[0];
	pci_write_config_dword(dev, aer + PCI_ERR_UNCOR_MASK, *cap++);
	pci_write_config_dword(dev, aer + PCI_ERR_UNCOR_SEVER, *cap++);
	pci_write_config_dword(dev, aer + PCI_ERR_COR_MASK, *cap++);
	pci_write_config_dword(dev, aer + PCI_ERR_CAP, *cap++);
	if (pcie_cap_has_rtctl(dev))
		pci_write_config_dword(dev, aer + PCI_ERR_ROOT_COMMAND, *cap++);
}

void pci_aer_init(struct pci_dev *dev)
{
	int n;

	dev->aer_cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR);
	if (!dev->aer_cap)
		return;

	dev->aer_stats = kzalloc(sizeof(struct aer_stats), GFP_KERNEL);

	/*
	 * We save/restore PCI_ERR_UNCOR_MASK, PCI_ERR_UNCOR_SEVER,
	 * PCI_ERR_COR_MASK, and PCI_ERR_CAP.  Root and Root Complex Event
	 * Collectors also implement PCI_ERR_ROOT_COMMAND (PCIe r5.0, sec
	 * 7.8.4).
	 */
	n = pcie_cap_has_rtctl(dev) ? 5 : 4;
	pci_add_ext_cap_save_buffer(dev, PCI_EXT_CAP_ID_ERR, sizeof(u32) * n);

	pci_aer_clear_status(dev);
}

void pci_aer_exit(struct pci_dev *dev)
{
	kfree(dev->aer_stats);
	dev->aer_stats = NULL;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Call Me Gavyn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值