2. PCI Express 端口总线驱动程序指南 HOWTO¶
- 作者:
Tom L Nguyen tom.l.nguyen@intel.com 2004 年 11 月 3 日
- 版权:
© 2004 Intel Corporation
2.1. 关于本指南¶
本指南描述了 PCI Express 端口总线驱动程序的基础知识,并提供了如何使服务驱动程序注册/注销 PCI Express 端口总线驱动程序的信息。
2.2. 什么是 PCI Express 端口总线驱动程序¶
PCI Express 端口是一种逻辑 PCI-PCI 桥结构。PCI Express 端口有两种类型:根端口和交换机端口。根端口从 PCI Express 根复合体发起 PCI Express 链路,交换机端口将 PCI Express 链路连接到内部逻辑 PCI 总线。交换机端口的次级总线代表交换机的内部路由逻辑,称为交换机的上行端口。交换机的下行端口从交换机的内部路由总线桥接到代表来自 PCI Express 交换机的下行 PCI Express 链路的总线。
PCI Express 端口可以提供多达四种不同的功能,在本文档中称为服务,具体取决于其端口类型。PCI Express 端口的服务包括原生热插拔支持 (HP)、电源管理事件支持 (PME)、高级错误报告支持 (AER) 和虚拟通道支持 (VC)。这些服务可以由一个复杂的驱动程序处理,也可以单独分发并由相应的服务驱动程序处理。
2.3. 为什么要使用 PCI Express 端口总线驱动程序?¶
在现有 Linux 内核中,Linux 设备驱动程序模型允许单个物理设备只能由一个驱动程序处理。PCI Express 端口是具有多个不同服务的 PCI-PCI 桥设备。为了保持一个清晰简单的解决方案,每个服务可以有自己的软件服务驱动程序。在这种情况下,多个服务驱动程序将争夺单个 PCI-PCI 桥设备。例如,如果 PCI Express 根端口原生热插拔服务驱动程序首先加载,它就会声明一个 PCI-PCI 桥根端口。因此,内核不会为该根端口加载其他服务驱动程序。换句话说,使用当前驱动程序模型,无法让多个服务驱动程序在 PCI-PCI 桥设备上同时加载和运行。
要实现多个服务驱动程序同时运行,需要一个 PCI Express 端口总线驱动程序,它管理所有已填充的 PCI Express 端口,并根据需要将所有提供的服务请求分发给相应的服务驱动程序。使用 PCI Express 端口总线驱动程序的一些主要优点如下所示:
允许多个服务驱动程序在 PCI-PCI 桥端口设备上同时运行。
允许服务驱动程序以独立的阶段式方法实现。
允许一个服务驱动程序在多个 PCI-PCI 桥端口设备上运行。
管理和分配 PCI-PCI 桥端口设备的资源给请求的服务驱动程序。
2.4. 配置 PCI Express 端口总线驱动程序与服务驱动程序¶
2.4.1. 将 PCI Express 端口总线驱动程序支持包含到内核中¶
是否包含 PCI Express 端口总线驱动程序取决于内核配置中是否包含 PCI Express 支持。当内核中启用 PCI Express 支持时,内核将自动将 PCI Express 端口总线驱动程序作为内核驱动程序包含在内。
2.4.2. 启用服务驱动程序支持¶
PCI 设备驱动程序是基于 Linux 设备驱动程序模型实现的。所有服务驱动程序都是 PCI 设备驱动程序。如上所述,一旦内核加载了 PCI Express 端口总线驱动程序,就无法加载任何服务驱动程序。为了满足 PCI Express 端口总线驱动程序模型,需要对现有服务驱动程序进行一些微小的更改,这些更改不会影响现有服务驱动程序的功能。
服务驱动程序需要使用下面显示的两个 API 将其服务注册到 PCI Express 端口总线驱动程序(参见 5.2.1 和 5.2.2 节)。重要的是,服务驱动程序在调用这些 API 之前,要初始化头文件 /include/linux/pcieport_if.h 中包含的 pcie_port_service_driver 数据结构。否则将导致身份不匹配,从而阻止 PCI Express 端口总线驱动程序加载服务驱动程序。
2.4.2.1. pcie_port_service_register¶
int pcie_port_service_register(struct pcie_port_service_driver *new)
此 API 替换了 Linux 驱动程序模型的 pci_register_driver API。服务驱动程序应始终在模块初始化时调用 pcie_port_service_register。请注意,服务驱动程序加载后,不再需要调用 pci_enable_device(dev) 和 pci_set_master(dev) 等函数,因为这些调用由 PCI 端口总线驱动程序执行。
2.4.2.2. pcie_port_service_unregister¶
void pcie_port_service_unregister(struct pcie_port_service_driver *new)
pcie_port_service_unregister 替换了 Linux 驱动程序模型的 pci_unregister_driver。当模块退出时,服务驱动程序总是会调用它。
2.4.2.3. 示例代码¶
下面是初始化端口服务驱动程序数据结构的服务驱动程序示例代码。
static struct pcie_port_service_id service_id[] = { {
.vendor = PCI_ANY_ID,
.device = PCI_ANY_ID,
.port_type = PCIE_RC_PORT,
.service_type = PCIE_PORT_SERVICE_AER,
}, { /* end: all zeroes */ }
};
static struct pcie_port_service_driver root_aerdrv = {
.name = (char *)device_name,
.id_table = service_id,
.probe = aerdrv_load,
.remove = aerdrv_unload,
.suspend = aerdrv_suspend,
.resume = aerdrv_resume,
};
下面是注册/注销服务驱动程序的示例代码。
static int __init aerdrv_service_init(void)
{
int retval = 0;
retval = pcie_port_service_register(&root_aerdrv);
if (!retval) {
/*
* FIX ME
*/
}
return retval;
}
static void __exit aerdrv_service_exit(void)
{
pcie_port_service_unregister(&root_aerdrv);
}
module_init(aerdrv_service_init);
module_exit(aerdrv_service_exit);
2.5. 可能的资源冲突¶
由于 PCI-PCI 桥端口设备的所有服务驱动程序都允许同时运行,下面列出了几种可能的资源冲突及其建议的解决方案。
2.5.1. MSI 和 MSI-X 向量资源¶
一旦在设备上启用 MSI 或 MSI-X 中断,它将保持此模式直到再次禁用。由于同一 PCI-PCI 桥端口的服务驱动程序共享相同的物理设备,如果单个服务驱动程序启用或禁用 MSI/MSI-X 模式,可能会导致不可预测的行为。
为避免这种情况,所有服务驱动程序均不允许切换其设备上的中断模式。PCI Express 端口总线驱动程序负责确定中断模式,这对于服务驱动程序来说应该是透明的。服务驱动程序只需知道分配给 struct pcie_device 结构体中 irq 字段的向量 IRQ,该 IRQ 在 PCI Express 端口总线驱动程序探测每个服务驱动程序时传入。服务驱动程序应使用 (struct pcie_device*)dev->irq 调用 request_irq/free_irq。此外,中断模式存储在 struct pcie_device 结构体的 interrupt_mode 字段中。
2.5.2. PCI 内存/IO 映射区域¶
PCI Express 电源管理 (PME)、高级错误报告 (AER)、热插拔 (HP) 和虚拟通道 (VC) 的服务驱动程序访问 PCI Express 端口上的 PCI 配置空间。在所有情况下,访问的寄存器都是相互独立的。此补丁假定所有服务驱动程序都将表现良好,并且不会覆盖其他服务驱动程序的配置设置。
2.5.3. PCI 配置寄存器¶
每个服务驱动程序都在自己的能力结构上执行 PCI 配置操作,除了 PCI Express 能力结构,该结构在包括服务驱动程序在内的许多驱动程序之间共享。RMW 能力访问器 (pcie_capability_clear_and_set_word(), pcie_capability_set_word(), 和 pcie_capability_clear_word()) 保护一组选定的 PCI Express 能力寄存器:
链路控制寄存器
根控制寄存器
链路控制 2 寄存器
对这些寄存器的任何更改都应使用 RMW 访问器执行,以避免由于并发更新而引起的问题。有关受保护寄存器的最新列表,请参阅 pcie_capability_clear_and_set_word()。