一、模块背景
调试PCIe AER错误恢复代码非常困难,因为它很难触发真正的硬件错误。基于软件的错误注入可用于伪造各种PCIe错误。
首先,应该在内核中启用PCIe AER软件错误注入配置,即以下应位于.config中的项目。
CONFIG_PCIEAER_INJECT = y或CONFIG_PCIEAER_INJECT = m
使用新内核重新启动或insmod模块后,名为设备文件/ dev / aer_inject 会被创建。
然后,需要一个名为aer-inject的用户空间工具,
该工具可以从此获取:git clone https://git.kernel.org/pub/scm/linux/kernel/git/gong.chen/aer-inject.git
Tips:
使用aer-inject工具的环境不能是虚拟机环境!!!
二、驱动介绍
linux内核版本:4.9.190。
aer inject的驱动文件:aer_inject.c,位于[drivers\pci\pcie\aer]。
模块的初始化入口:aer_inject_init,直接注册了一个混杂设备驱动。

static int __init aer_inject_init(void)
	return misc_register(&aer_inject_device);

然后直接找到file_operations结构体,主要增加了aer_inject_write这个写接口。

static struct miscdevice aer_inject_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "aer_inject",
	.fops = &aer_inject_fops,
static const struct file_operations aer_inject_fops = {
	.write = aer_inject_write,
	.owner = THIS_MODULE,
	.llseek = noop_llseek,

当用户调用设备节点进行写操作时,aer_inject_write接口函数将被调用,aer_inject_write里首先检查权限,只有root用户才能正常操作,然后将用户空间的程序写的内容复制到内核空间,最后调用aer_inject函数继续处理。
在函数aer_inject里,首先调用pci_get_domain_bus_and_slot寻找需要inject设备的pci_dev结构体。

dev = pci_get_domain_bus_and_slot(einj->domain, einj->bus, devfn);
	if (!dev)
		return -ENODEV;
rpdev = pcie_find_root_port(dev);
	if (!rpdev) {
		dev_err(&dev->dev, "aer_inject: Root port not found\n");
		ret = -ENODEV;
		goto out_put;

然后再寻找根端口的pci_dev结构体。
接下来调用pci_find_ext_capability来确认是否支持AER功能。

pos_cap_err = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR);
if (!pos_cap_err) {
	dev_err(&dev->dev, "aer_inject: Device doesn't support AER\n");
	ret = -EPROTONOSUPPORT;
	goto out_put;
pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_SEVER, &sever);
pci_read_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK, &cor_mask);
pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK,
		      &uncor_mask);
rp_pos_cap_err = pci_find_ext_capability(rpdev, PCI_EXT_CAP_ID_ERR);
if (!rp_pos_cap_err) {
	dev_err(&rpdev->dev,
		"aer_inject: Root port doesn't support AER\n");
	ret = -EPROTONOSUPPORT;
	goto out_put;

然后调用__find_aer_error_by_dev函数寻找dev设备的aer_error结构体,并对其内容进行设置。

err = __find_aer_error_by_dev(dev);
	if (!err) {
		err = err_alloc;
		err_alloc = NULL;
		aer_error_init(err, einj->domain, einj->bus, devfn,
			       pos_cap_err);
		list_add(&err->list, &einjected);
	err->uncor_status |= einj->uncor_status;
	err->cor_status |= einj->cor_status;
	err->header_log0 = einj->header_log0;
	err->header_log1 = einj->header_log1;
	err->header_log2 = einj->header_log2;
	err->header_log3 = einj->header_log3;

接下来寻找根端口设备的aer_error结构体,并对可校正错误和不可校正错误的状态进行设置。

rperr = __find_aer_error_by_dev(rpdev);
	if (!rperr) {
		rperr = rperr_alloc;
		rperr_alloc = NULL;
		aer_error_init(rperr, pci_domain_nr(rpdev->bus),
			       rpdev->bus->number, rpdev->devfn,
			       rp_pos_cap_err);
		list_add(&rperr->list, &einjected);
	if (einj->cor_status) {
		if (rperr->root_status & PCI_ERR_ROOT_COR_RCV)
			rperr->root_status |= PCI_ERR_ROOT_MULTI_COR_RCV;
			rperr->root_status |= PCI_ERR_ROOT_COR_RCV;
		rperr->source_id &= 0xffff0000;
		rperr->source_id |= (einj->bus << 8) | devfn;
	if (einj->uncor_status) {
		if (rperr->root_status & PCI_ERR_ROOT_UNCOR_RCV)
			rperr->root_status |= PCI_ERR_ROOT_MULTI_UNCOR_RCV;
		if (sever & einj->uncor_status) {
			rperr->root_status |= PCI_ERR_ROOT_FATAL_RCV;
			if (!(rperr->root_status & PCI_ERR_ROOT_UNCOR_RCV))
				rperr->root_status |= PCI_ERR_ROOT_FIRST_FATAL;
		} else
			rperr->root_status |= PCI_ERR_ROOT_NONFATAL_RCV;
		rperr->root_status |= PCI_ERR_ROOT_UNCOR_RCV;
		rperr->source_id &= 0x0000ffff;
		rperr->source_id |= ((einj->bus << 8) | devfn) << 16;

接下来调用pci_bus_set_aer_ops来设置设备总线的,主要就是设置读写的结构体,这个地方也就是inject的精华之处。

ret = pci_bus_set_aer_ops(dev->bus);
	if (ret)
		goto out_put;
	ret = pci_bus_set_aer_ops(rpdev->bus);
	if (ret)
		goto out_put;
static int pci_bus_set_aer_ops(struct pci_bus *bus)
	struct pci_ops *ops;
	struct pci_bus_ops *bus_ops;
	unsigned long flags;
	bus_ops = kmalloc(sizeof(*bus_ops), GFP_KERNEL);
	if (!bus_ops)
		return -ENOMEM;
	ops = pci_bus_set_ops(bus, &aer_inj_pci_ops);
	spin_lock_irqsave(&inject_lock, flags);
	if (ops == &aer_inj_pci_ops)
		goto out;
	pci_bus_ops_init(bus_ops, bus, ops);
	list_add(&bus_ops->list, &pci_bus_ops_list);
	bus_ops = NULL;
out:
	spin_unlock_irqrestore(&inject_lock, flags);
	kfree(bus_ops);
	return 0;
static struct pci_ops aer_inj_pci_ops = {
	.read = aer_inj_read_config,
	.write = aer_inj_write_config,

以aer_inj_read_config为例,在find_pci_config_dword里面实际上替换了之前设置的内容,这就使得独处的错误为用户设定的错误。

static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn,
			       int where, int size, u32 *val)
	u32 *sim;
	struct aer_error *err;
	unsigned long flags;
	struct pci_ops *ops;
	struct pci_ops *my_ops;
	int domain;
	int rv;
	spin_lock_irqsave(&inject_lock, flags);
	if (size != sizeof(u32))
		goto out;
	domain = pci_domain_nr(bus);
	if (domain < 0)
		goto out;
	err = __find_aer_error(domain, bus->number, devfn);
	if (!err)
		goto out;
	sim = find_pci_config_dword(err, where, NULL);
	if (sim) {
		*val = *sim;
		spin_unlock_irqrestore(&inject_lock, flags);
		return 0;
out:
	ops = __find_pci_bus_ops(bus);
	 * pci_lock must already be held, so we can directly
	 * manipulate bus->ops.  Many config access functions,
	 * including pci_generic_config_read() require the original
	 * bus->ops be installed to function, so temporarily put them
	 * back.
	my_ops = bus->ops;
	bus->ops = ops;
	rv = ops->read(bus, devfn, where, size, val);
	bus->ops = my_ops;
	spin_unlock_irqrestore(&inject_lock, flags);
	return rv;
static u32 *find_pci_config_dword(struct aer_error *err, int where,
				  int *prw1cs)
	int rw1cs = 0;
	u32 *target = NULL;
	if (err->pos_cap_err == -1)
		return NULL;
	switch (where - err->pos_cap_err) {
	case PCI_ERR_UNCOR_STATUS:
		target = &err->uncor_status;
		rw1cs = 1;
		break;
	case PCI_ERR_COR_STATUS:
		target = &err->cor_status;
		rw1cs = 1;
		break;
	case PCI_ERR_HEADER_LOG:
		target = &err->header_log0;
		break;
	case PCI_ERR_HEADER_LOG+4:
		target = &err->header_log1;
		break;
	case PCI_ERR_HEADER_LOG+8:
		target = &err->header_log2;
		break;
	case PCI_ERR_HEADER_LOG+12:
		target = &err->header_log3;
		break;
	case PCI_ERR_ROOT_STATUS:
		target = &err->root_status;
		rw1cs = 1;
		break;
	case PCI_ERR_ROOT_ERR_SRC:
		target = &err->source_id;
		break;
	if (prw1cs)
		*prw1cs = rw1cs;
	return target;

最后再调用aer_irq(-1, edev);模拟中断产生,然后走常规的PCIe AER检测机制。

AER 即 Advanced Error Reporting高级错误报告,是PCIe高级特性,用于报告PCIe 错误信息,是PCIe RAS特性最重要的部分,本文从PCIe AER协议、固件、linux内核实现讲述PCIe AER知识。 CONFIG_CROSS_COMPILE 交叉编译工具前缀(比如"arm-linux-"相当于使用"make CROSS_COMPILE=arm-linux-"进行编译).除非你想配置后默认自动进行交叉编译,否则不要使用此选项. Local version - append to kernel rel... 前面的文章提到过高级错误报告(Advanced Error Reporting,AER),接下来详细地介绍一下这一功能。在已有的PCIe错误报告机制上(之前文章介绍的),AER还支持以下特性: ·         在登记实际发生的错误类型时 DPC的全称是downstream port containment,是针对root port和pcie switch检测到不可恢复的错误时,就会通知下游端口的业务,以防止数据损坏的扩散. 其代码在drivers/pci/pcie/dpc.c static struct pcie_port_service_driver dpcdriver = { .name = "dpc", .port_... 什么是AER AER 英文简称 Advanced Error Reporting 翻译中文是高级错误报告,是PCIE异常信息处理机制,用于报告PCIe 错误信息 错误信息主要分为两种 Correctable Errors 和Uncorrectable errors 其中 Correctable Errors包含非致命的错误和致命的错误 a.ERR_FATAL:致命错误,此错误类型影响了PCIe link链路。 b.ERR_NONFATAL:指影响了设备功能,但是PCIe 在drivers/pci/probe.c 中的pci_init_capabilities函数中调用pci_iov_init 来进行sriov的初始化 int pci_iov_init(struct pci_dev *dev)     int pos;     if (!pci_is_pcie(dev))         return -ENODEV;     pos = p CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。 本博文对应地址:https://hceng.cn/2019/09/05/Buildroot%E7%AC%94%E8%AE%B0/#more 整理Buildroot笔记,包含配置选项注释、目录结构分析、常用命令、构建示例、 使用技巧。 1.Buildroot基本介绍 Buildroot是Linu... 本文翻译自内核文档:linux\Documentation\PCI\pcieaer-howto.txt 《 PCI Express高级错误报告驱动程序指南》 HOWTO T.Long Nguyen <tom.l.nguyen@intel.com> 张艳敏<yanmin.zhang@intel.com> 2006年7月29日 1.1关于本指南