PCIe AER的INJECT机制

一、模块背景
调试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;
		else
			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检测机制。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值