2. PCI Express 端口总线驱动指南 HOWTO

作者:

Tom L Nguyen tom.l.nguyen@intel.com 2004/11/03

版权:

© 2004 英特尔公司

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 之前初始化 pcie_port_service_driver 数据结构,该数据结构包含在头文件 /include/linux/pcieport_if.h 中。否则将导致身份不匹配,从而阻止 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,当 PCI Express 端口总线驱动程序探测每个服务驱动程序时会传递该向量 IRQ。服务驱动程序应使用 (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()。