不同的内核版本对PCIe的AER机制有微妙的差别,本次研究基于linux内核版本:4.9.190。
驱动文件的目录:[drivers\pci\pcie\aer]、[drivers\pci\pcie]。
PCIe AER的内核模块初始化的位置在[aerdrv.c]。
驱动初始化入口:device_initcall(aer_service_init);
static int __init aer_service_init(void)
{
if (!pci_aer_available() || aer_acpi_firmware_first())
return -ENXIO;
return pcie_port_service_register(&aerdriver);
}
在aer_service_init里首先要判断能否注册aer驱动。
在pci_aer_available-> pci_msi_enabled检查msi中断是否使能,以及使用aer_acpi_firmware_first函数确认将AER上报给固件还是操作系统。AER支持firmware-first和Natvie两种处理方式,firmware-first即固件优先,Native方式直接报给操作系统。最后调用pcie_port_service_register进行驱动的注册。
函数pcie_port_service_register封装了通用注册,也就是再加一层外壳,我们重点看aerdriver。
static struct pcie_port_service_driver aerdriver = {
.name = "aer",
.port_type = PCI_EXP_TYPE_ROOT_PORT,
.service = PCIE_PORT_SERVICE_AER,
.probe = aer_probe,
.remove = aer_remove,
.err_handler = &aer_error_handlers,
.reset_link = aer_root_reset,
};
抓住主干,我们看一下aer_probe。
static int aer_probe(struct pcie_device *dev)
{
int status;
struct aer_rpc *rpc;
struct device *device = &dev->device;
/* Alloc rpc data structure */
rpc = aer_alloc_rpc(dev);
if (!rpc) {
dev_printk(KERN_DEBUG, device, "alloc rpc failed\n");
aer_remove(dev);
return -ENOMEM;
}
/* Request IRQ ISR */
status = request_irq(dev->irq, aer_irq, IRQF_SHARED, "aerdrv", dev);
if (status) {
dev_printk(KERN_DEBUG, device, "request IRQ failed\n");
aer_remove(dev);
return status;
}
rpc->isr = 1;
aer_enable_rootport(rpc);
return status;
}
不要被aer_alloc_rpc函数的命令给欺骗了,这个函数里额外做了一个初始化工作队列aer_isr,留给中断下半部进行调用。
static struct aer_rpc *aer_alloc_rpc(struct pcie_device *dev)
{
struct aer_rpc *rpc;
rpc = kzalloc(sizeof(struct aer_rpc), GFP_KERNEL);
if (!rpc)
return NULL;
/* Initialize Root lock access, e_lock, to Root Error Status Reg */
spin_lock_init(&rpc->e_lock);
rpc->rpd = dev;
INIT_WORK(&rpc->dpc_handler, aer_isr);
mutex_init(&rpc->rpc_mutex);
/* Use PCIe bus function to store rpc into PCIe device */
set_service_data(dev, rpc);
return rpc;
}
在aer_probe里使用request_irq注册了一个中断处理函数,中断处理程序为aer_irq,在中断处理程序里面首先读取一些寄存器的状态,然后使用schedule_work函数来申请调度之前初始化的工作队列。
在aer_probe的最后使用aer_enable_rootport函数来使能根端口的消息中断功能。
我们接下来要看工作队列的handler:aer_isr。
aer_isr唯一的工作就是处理根端口检测到的错误。主要工作在aer_isr_one_error里完成。
static void aer_isr_one_error(struct pcie_device *p_device,
struct aer_err_source *e_src)
{
struct aer_rpc *rpc = get_service_data(p_device);
struct aer_err_info *e_info = &rpc->e_info;
/*
* 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) {
e_info->id = ERR_COR_ID(e_src->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(p_device->port, e_info);
if (find_source_device(p_device->port, e_info))
aer_process_err_devices(p_device, e_info);
}
if (e_src->status & PCI_ERR_ROOT_UNCOR_RCV) {
e_info->id = ERR_UNCOR_ID(e_src->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(p_device->port, e_info);
if (find_source_device(p_device->port, e_info))
aer_process_err_devices(p_device, e_info);
}
}
有些错误中既包含可校正的错误,又包含不可校正的错误,在aer_isr_one_error中程序首先处理的可校正的错误,不知道为什么,可能是有些人喜欢开始就干出力小的活的原因吧。
可校正的错误和不可校正的错误的处理流程差不多,都是先调用aer_print_port_info进行错误消息输出,然后在调用aer_process_err_devices进行处理,就是不可校正的错误里面根据严重程序分为了致命错误和非致命错误。
static inline void aer_process_err_devices(struct pcie_device *p_device,
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 (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 (get_device_error_info(e_info->dev[i], e_info))
handle_error_source(p_device, e_info->dev[i], e_info);
}
}
在aer_process_err_devices里主要就是调用handle_error_source进行进一步的处理,其实就是封装了几个外壳。
static void handle_error_source(struct pcie_device *aerdev,
struct pci_dev *dev,
struct aer_err_info *info)
{
int pos;
if (info->severity == AER_CORRECTABLE) {
/*
* Correctable error does not need software intervention.
* No need to go through error recovery process.
*/
pos = dev->aer_cap;
if (pos)
pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS,
info->status);
} else
do_recovery(dev, info->severity);
}
在handle_error_source我们发现,对于可校正的错误,软件并没有做实质性的操作,因为PCIe的硬件自动修复该类错误。有些人就好奇了,硬件能自动修复的错误为什么还要上报呢,有这个必要吗?其实还是有必要的,至少可以告诉你有这一类的错误存在,说不定哪天就成为不可校正的错误了呢。举个不知道恰不恰当的例子,就像一个人得肾结石,开始也就是轻微的疼痛,过一会就不疼了,一般人也不当回事,但是如果长期不重视,就会演化为严重的肾结石,疼痛难忍,肚子里积水,甚至致命。所以我们也要关注一下可校正的错误。
不可校正的错误则调用do_recovery的函数进行处理,这个函数也是AER的核心函数。
static void do_recovery(struct pci_dev *dev, int severity)
{
pci_ers_result_t status, result = PCI_ERS_RESULT_RECOVERED;
enum pci_channel_state state;
if (severity == AER_FATAL)
state = pci_channel_io_frozen;
else
state = pci_channel_io_normal;
status = broadcast_error_message(dev,
state,
"error_detected",
report_error_detected);
if (severity == AER_FATAL) {
result = reset_link(dev);
if (result != PCI_ERS_RESULT_RECOVERED)
goto failed;
}
if (status == PCI_ERS_RESULT_CAN_RECOVER)
status = broadcast_error_message(dev,
state,
"mmio_enabled",
report_mmio_enabled);
if (status == PCI_ERS_RESULT_NEED_RESET) {
/*
* TODO: Should call platform-specific
* functions to reset slot before calling
* drivers' slot_reset callbacks?
*/
status = broadcast_error_message(dev,
state,
"slot_reset",
report_slot_reset);
}
if (status != PCI_ERS_RESULT_RECOVERED)
goto failed;
broadcast_error_message(dev,
state,
"resume",
report_resume);
dev_info(&dev->dev, "AER: Device recovery successful\n");
return;
failed:
/* TODO: Should kernel panic here? */
dev_info(&dev->dev, "AER: Device recovery failed\n");
}
在do_recovery中首先根据是否致命来确定PCI的通道是否正常。然后调用broadcast_error_message往下游设备的驱动发送消息。
如果错误是致命的,则调用reset_link重置一下link链路;
如果状态是可以恢复,则往下游设备发消息处理,并让下游设备的mmio使能;
如果状态需要重置,则同样往下游设备发消息处理,并让下游设备的pci slo重新reset一下;
如果状态是可恢复,则同样往下游设备发消息处理,并让下游设备的pci设备重新resume一下;
但是如果不能恢复,则AER能干的活也就到这了,what a pity!
具体的reset_link和broadcast_error_message就没有必要跟了,有兴趣的话可以继续深入了解。