目录, 显示框架, 无框架

第 6 章
PCI


外围组件互连 (PCI),顾名思义,是一种描述如何以结构化和受控方式将系统外围组件连接在一起的标准。该标准描述了系统组件的电气连接方式以及它们应有的行为方式。本章着眼于 Linux 内核如何初始化系统的 PCI 总线和设备。


图 6.1:基于 PCI 的系统示例

图  6.1 是一个基于 PCI 的系统示例的逻辑图。PCI 总线和 PCI-PCI 桥接器是将系统组件连接在一起的粘合剂;CPU 连接到 PCI 总线 0,即主 PCI 总线,视频设备也是如此。一个特殊的 PCI 设备,即 PCI-PCI 桥接器,将主总线连接到辅助 PCI 总线,即 PCI 总线 1。在 PCI 规范的术语中,PCI 总线 1 被描述为 PCI-PCI 桥接器的下游,而 PCI 总线 0 是桥接器的上游。连接到辅助 PCI 总线的是系统的 SCSI 和以太网设备。在物理上,桥接器、辅助 PCI 总线和两个设备都将包含在同一组合 PCI 卡上。系统中的 PCI-ISA 桥接器支持较旧的传统 ISA 设备,该图显示了一个超级 I/O 控制器芯片,该芯片控制键盘、鼠标和软盘。1

6.1  PCI 地址空间

CPU 和 PCI 设备需要访问它们之间共享的内存。设备驱动程序使用此内存来控制 PCI 设备并在它们之间传递信息。通常,共享内存包含设备的控制和状态寄存器。这些寄存器用于控制设备和读取其状态。例如,PCI SCSI 设备驱动程序会读取其状态寄存器,以了解 SCSI 设备是否已准备好将信息块写入 SCSI 磁盘。或者,它可能会写入控制寄存器以在设备开启后启动设备运行。

CPU 的系统内存可以用于此共享内存,但如果是这样,那么每次 PCI 设备访问内存时,CPU 都必须停顿,等待 PCI 设备完成。对内存的访问通常一次仅限于一个系统组件。这将减慢系统速度。允许系统的外围设备以不受控制的方式访问主内存也不是一个好主意。这将非常危险;一个流氓设备可能会使系统非常不稳定。

外围设备有自己的内存空间。CPU 可以访问这些空间,但设备对系统内存的访问受到 DMA(直接内存访问)通道的严格控制。ISA 设备可以访问两个地址空间:ISA I/O(输入/输出)和 ISA 内存。PCI 有三个:PCI I/O、PCI 内存和 PCI 配置空间。所有这些地址空间也都可以被 CPU 访问,其中 PCI I/O 和 PCI 内存地址空间由设备驱动程序使用,而 PCI 配置空间由 Linux 内核中的 PCI 初始化代码使用。

Alpha AXP 处理器不自然地访问系统地址空间以外的地址空间。它使用支持芯片组来访问其他地址空间,例如 PCI 配置空间。它使用稀疏地址映射方案,该方案窃取大型虚拟地址空间的一部分并将其映射到 PCI 地址空间。

6.2  PCI 配置标头


图 6.2:PCI 配置标头

系统中的每个 PCI 设备,包括 PCI-PCI 桥接器,在 PCI 配置地址空间中的某个位置都有一个配置数据结构。PCI 配置标头允许系统识别和控制设备。标头在 PCI 配置地址空间中的确切位置取决于设备在 PCI 拓扑结构中的位置。例如,插入 PC 主板上的一个 PCI 插槽的 PCI 视频卡,其配置标头将位于一个位置,如果将其插入另一个 PCI 插槽,则其标头将出现在 PCI 配置内存中的另一个位置。这无关紧要,因为无论 PCI 设备和桥接器位于何处,系统都将使用其配置标头中的状态和配置寄存器来查找和配置它们。

通常,系统的设计使得每个 PCI 插槽的 PCI 配置标头都位于与其在板上的插槽相关的偏移量处。因此,例如,板上的第一个插槽的 PCI 配置可能位于偏移量 0 处,第二个插槽的 PCI 配置可能位于偏移量 256 处(所有标头的长度都相同,为 256 字节),依此类推。定义了一个特定于系统的硬件机制,以便 PCI 配置代码可以尝试检查给定 PCI 总线的所有可能的 PCI 配置标头,并通过尝试读取标头中的一个字段(通常是供应商标识字段)并获得某种错误来了解哪些设备存在,哪些设备不存在。该描述将一种可能的错误消息描述为在尝试读取空 PCI 插槽的供应商标识设备标识字段时返回 0xFFFFFFFF

图  6.2 显示了 256 字节 PCI 配置标头的布局。它包含以下字段

供应商标识
描述 PCI 设备始发者的唯一编号。Digital 的 PCI 供应商标识为 0x1011,而 Intel 的为 0x8086
设备标识
描述设备本身的唯一编号。例如,Digital 的 21141 快速以太网设备的设备标识为 0x0009
状态
此字段给出设备的状态,此字段的位含义由标准设置。
命令
通过写入此字段,系统可以控制设备,例如允许设备访问 PCI I/O 内存,
类代码
这标识了设备的类型。每种设备都有标准类;视频、SCSI 等等。SCSI 的类代码为 0x0100
基地址寄存器
这些寄存器用于确定和分配设备可以使用的 PCI I/O 和 PCI 内存空间的类型、数量和位置。
中断引脚
PCI 卡上的四个物理引脚将中断从卡传输到 PCI 总线。标准将它们标记为 A、B、C 和 D。中断引脚字段描述了此 PCI 设备使用哪个引脚。通常,它是为特定设备硬连线的。也就是说,每次系统启动时,设备都使用相同的中断引脚。此信息允许中断处理子系统管理来自此设备的中断,
中断线
设备 PCI 配置标头的中断线字段用于在 PCI 初始化代码、设备驱动程序和 Linux 的中断处理子系统之间传递中断句柄。写入此处的数字对于设备驱动程序来说没有意义,但它允许中断处理程序将来自 PCI 设备的中断正确路由到 Linux 操作系统中正确设备驱动程序的中断处理代码。有关 Linux 如何处理中断的详细信息,请参见第  interrupt-chapter 章,第 页。

6.3  PCI I/O 和 PCI 内存地址

设备使用这两个地址空间与其在 CPU 上运行的 Linux 内核中的设备驱动程序进行通信。例如,DECchip 21141 快速以太网设备将其内部寄存器映射到 PCI I/O 空间。然后,其 Linux 设备驱动程序读取和写入这些寄存器以控制设备。视频驱动程序通常使用大量的 PCI 内存空间来包含视频信息。

在 PCI 系统设置完成并且使用 PCI 配置标头中的命令字段打开设备对这些地址空间的访问之前,任何人都无法访问它们。应该注意的是,只有 PCI 配置代码读取和写入 PCI 配置地址;Linux 设备驱动程序仅读取和写入 PCI I/O 和 PCI 内存地址。

6.4  PCI-ISA 桥接器

这些桥接器通过将 PCI I/O 和 PCI 内存空间访问转换为 ISA I/O 和 ISA 内存访问来支持传统的 ISA 设备。现在销售的许多系统都包含多个 ISA 总线插槽和多个 PCI 总线插槽。随着时间的推移,对这种向后兼容性的需求将减少,并且将销售仅 PCI 系统。在 ISA 地址空间(I/O 和内存)中,系统的 ISA 设备注册其寄存器的位置在早期的基于 Intel 8080 的 PC 的模糊时代就已固定下来。即使是价值 5000 美元的基于 Alpha AXP 的计算机系统,其 ISA 软盘控制器也将与第一台 IBM PC 位于 ISA I/O 空间中的相同位置。PCI 规范通过保留 PCI I/O 和 PCI 内存地址空间的较低区域供系统中的 ISA 外围设备使用,并使用单个 PCI-ISA 桥接器将任何 PCI 内存访问转换为对这些区域的 ISA 访问来应对这种情况。

6.5  PCI-PCI 桥接器

PCI-PCI 桥接器是特殊的 PCI 设备,可将系统的 PCI 总线粘合在一起。简单系统只有一个 PCI 总线,但单个 PCI 总线可以支持的 PCI 设备数量存在电气限制。使用 PCI-PCI 桥接器添加更多 PCI 总线允许系统支持更多 PCI 设备。这对于高性能服务器尤其重要。当然,Linux 完全支持 PCI-PCI 桥接器的使用。

6.5.1  PCI-PCI 桥接器:PCI I/O 和 PCI 内存窗口

PCI-PCI 桥接器仅向下游传递 PCI I/O 和 PCI 内存读取和写入请求的子集。例如,在图  6.1 中,第 pageref 页,PCI-PCI 桥接器仅在从 PCI 总线 0 到 PCI 总线 1 的读取和写入地址是 SCSI 或以太网设备拥有的 PCI I/O 或 PCI 内存地址时才传递;所有其他 PCI I/O 和内存地址都被忽略。这种过滤阻止了地址在整个系统中不必要地传播。为此,必须为 PCI-PCI 桥接器编程一个基址和限制,用于 PCI I/O 和 PCI 内存空间访问,它们必须从其主总线传递到其辅助总线。一旦系统中的 PCI-PCI 桥接器配置完成,只要 Linux 设备驱动程序仅通过这些窗口访问 PCI I/O 和 PCI 内存空间,PCI-PCI 桥接器就是不可见的。这是一个重要的功能,使 Linux PCI 设备驱动程序编写人员的生活更轻松。但是,这也使得 Linux 配置 PCI-PCI 桥接器变得有些棘手,我们将在稍后看到。

6.5.2  PCI-PCI 桥接器:PCI 配置周期和 PCI 总线编号


图 6.3:类型 0 PCI 配置周期


图 6.4:类型 1 PCI 配置周期

为了使 CPU 的 PCI 初始化代码能够寻址不在主 PCI 总线上的设备,必须有一种机制,允许桥接器决定是否将配置周期从其主接口传递到其辅助接口。周期只是 PCI 总线上出现的地址。PCI 规范定义了 PCI 配置地址的两种格式;类型 0 和类型 1;这些分别在图  6.3 和图  6.4 中显示。类型 0 PCI 配置周期不包含总线号,所有设备都将其解释为用于此 PCI 总线上的 PCI 配置地址。类型 0 配置周期的位 31:11 被视为设备选择字段。设计系统的一种方法是让每个位选择不同的设备。在这种情况下,位 11 将选择插槽 0 中的 PCI 设备,位 12 将选择插槽 1 中的 PCI 设备,依此类推。另一种方法是将设备的插槽号直接写入位 31:11。系统中使用的机制取决于系统的 PCI 内存控制器。

类型 1 PCI 配置周期包含 PCI 总线号,除了 PCI-PCI 桥接器之外,所有 PCI 设备都忽略这种类型的配置周期。所有看到类型 1 配置周期的 PCI-PCI 桥接器都可以选择将其传递到其下游的 PCI 总线。PCI-PCI 桥接器是忽略类型 1 配置周期还是将其传递到下游 PCI 总线取决于 PCI-PCI 桥接器的配置方式。每个 PCI-PCI 桥接器都有一个主总线接口号和一个辅助总线接口号。主总线接口是最靠近 CPU 的接口,辅助总线接口是最远的接口。每个 PCI-PCI 桥接器还有一个从属总线号,这是桥接器辅助总线接口之外所有桥接 PCI 总线的最大总线号。换句话说,从属总线号是 PCI-PCI 桥接器下游编号最高的 PCI 总线。当 PCI-PCI 桥接器看到类型 1 PCI 配置周期时,它会执行以下操作之一

因此,如果我们想寻址拓扑图  pci-pci-config-eg-4 第 页上的总线 3 上的设备 1,我们必须从 CPU 生成类型 1 配置命令。桥接器1 将此命令不变地传递到总线 1。桥接器2 忽略它,但桥接器3 将其转换为类型 0 配置命令,并将其发送到总线 3,设备 1 在总线 3 上响应它。

在 PCI 配置期间分配总线号是每个操作系统的责任,但无论使用何种编号方案,对于系统中的所有 PCI-PCI 桥接器,以下陈述都必须为真

``位于 PCI-PCI 桥接器后面的所有 PCI 总线都必须位于辅助总线号和从属总线号之间(包括端点值)。''

如果违反此规则,则 PCI-PCI 桥接器将无法正确传递和转换类型 1 PCI 配置周期,并且系统将无法找到和初始化系统中的 PCI 设备。为了实现此编号方案,Linux 以特定顺序配置这些特殊设备。第  pci-pci-bus-numbering 节,第 页,详细描述了 Linux 的 PCI 桥接器和总线编号方案,以及一个工作示例。

6.6  Linux PCI 初始化

Linux 中的 PCI 初始化代码分为三个逻辑部分

PCI 设备驱动程序
此伪设备驱动程序从总线 0 开始搜索 PCI 系统,并找到系统中的所有 PCI 设备和桥接器。它构建一个链表,其中包含描述系统拓扑结构的数据结构。此外,它还对找到的所有桥接器进行编号。

PCI BIOS
此软件层提供 bib-pci-bios-specification 中描述的服务。即使 Alpha AXP 没有 BIOS 服务,Linux 内核中也有等效的代码提供相同的功能,

PCI 修复
系统特定的修复代码清理了 PCI 初始化中特定于系统的零星问题。

6.6.1  Linux 内核 PCI 数据结构


图 6.5:Linux 内核 PCI 数据结构

当 Linux 内核初始化 PCI 系统时,它会构建数据结构,以镜像系统的真实 PCI 拓扑结构。图  6.5 显示了它将为图  6.1 第 pageref 页中的示例 PCI 系统构建的数据结构的关系。

每个 PCI 设备(包括 PCI-PCI 桥接器)都由一个pci_dev数据结构描述。每个 PCI 总线都由一个pci_bus数据结构描述。结果是一个 PCI 总线的树状结构,每个总线都连接了许多子 PCI 设备。由于 PCI 总线只能通过 PCI-PCI 桥接器访问(主 PCI 总线,总线 0 除外),因此每个pci_bus都包含一个指向 PCI 设备(PCI-PCI 桥接器)的指针,它通过该桥接器访问。该 PCI 设备是 PCI 总线的父 PCI 总线的子设备。

图  6.5 中未显示的是指向系统中所有 PCI 设备的指针,pci_devices。系统中所有 PCI 设备都将其pci_dev数据结构排队到此队列中。Linux 内核使用此队列来快速查找系统中的所有 PCI 设备。

6.6.2  PCI 设备驱动程序

PCI 设备驱动程序实际上根本不是设备驱动程序,而是在系统初始化时调用的操作系统功能。PCI 初始化代码必须扫描系统中的所有 PCI 总线,以查找系统中的所有 PCI 设备(包括 PCI-PCI 桥接器设备)。

它使用 PCI BIOS 代码来了解它正在扫描的当前 PCI 总线中的每个可能的插槽是否被占用。如果 PCI 插槽被占用,它会构建一个pci_dev数据结构,描述设备并链接到已知 PCI 设备列表(由pci_devices).

指向)。PCI 初始化代码首先扫描 PCI 总线 0。它尝试读取每个可能的 PCI 插槽中每个可能的 PCI 设备的供应商标识设备标识字段。当它找到一个被占用的插槽时,它会构建一个pci_dev数据结构,描述设备。PCI 初始化代码构建的所有pci_dev数据结构(包括所有 PCI-PCI 桥接器)都链接到一个单链表中;pci_devices.

如果找到的 PCI 设备是 PCI-PCI 桥接器,则会构建一个pci_bus数据结构,并链接到pci_buspci_dev的树中,由pci_root指向。PCI 初始化代码可以判断 PCI 设备是否为 PCI-PCI 桥接器,因为它具有 0x060400 的类代码。然后,Linux 内核配置刚刚找到的 PCI-PCI 桥接器另一侧(下游)的 PCI 总线。如果找到更多 PCI-PCI 桥接器,也会配置这些桥接器。此过程称为深度优先算法;系统的 PCI 拓扑结构在广度优先搜索之前完全按深度映射。查看图  6.1,第 pageref 页,Linux 将配置 PCI 总线 1 及其以太网和 SCSI 设备,然后再配置 PCI 总线 0 上的视频设备。

当 Linux 搜索下游 PCI 总线时,它还必须配置中间 PCI-PCI 桥接器的辅助总线号和从属总线号。这在下面的第  pci-pci-bus-numbering 节中详细描述。

配置 PCI-PCI 桥接器 - 分配 PCI 总线号


图 6.6:配置 PCI 系统:第 1 部分

为了使 PCI-PCI 桥接器能够在其上传递 PCI I/O、PCI 内存或 PCI 配置地址空间读取和写入,它们需要知道以下内容

主总线号
PCI-PCI 桥接器正上游的总线号,
辅助总线号
PCI-PCI 桥接器正下游的总线号,
从属总线号
桥接器下游可以到达的所有总线的最高总线号。
PCI I/O 和 PCI 内存窗口
PCI I/O 地址空间和 PCI 内存地址空间的窗口基址和大小,用于 PCI-PCI 桥接器下游的所有地址。

问题在于,当您希望配置任何给定的 PCI-PCI 桥接器时,您不知道该桥接器的从属总线号。您不知道下游是否还有其他 PCI-PCI 桥接器,即使知道,您也不知道将分配给它们的编号。答案是使用深度优先递归算法,并扫描每个总线以查找任何 PCI-PCI 桥接器,并在找到它们时为其分配编号。当找到每个 PCI-PCI 桥接器并为其辅助总线编号时,为其分配一个临时从属编号 0xFF,并扫描并为其下游的所有 PCI-PCI 桥接器分配编号。这一切看起来都很复杂,但下面的工作示例使此过程更加清晰。

PCI-PCI 桥接器编号:步骤 1
采用图  6.6 中的拓扑结构,扫描将找到的第一个桥接器是桥接器1。桥接器1 下游的 PCI 总线将被编号为 1,桥接器1 被分配辅助总线号 1 和临时从属总线号 0xFF。这意味着所有指定 PCI 总线号为 1 或更高的类型 1 PCI 配置地址都将通过桥接器1 并传递到 PCI 总线 1 上。如果它们的总线号为 1,则它们将被转换为类型 0 配置周期,但对于所有其他总线号,则保持未转换。这正是 Linux PCI 初始化代码为了扫描 PCI 总线 1 而需要做的。


图 6.7:配置 PCI 系统:第 2 部分

PCI-PCI 桥接器编号:步骤 2
Linux 使用深度优先算法,因此初始化代码继续扫描 PCI 总线 1。在这里,它找到 PCI-PCI 桥接器2。PCI-PCI 桥接器2 之外没有其他 PCI-PCI 桥接器,因此为其分配从属总线号 2,该编号与分配给其辅助接口的编号匹配。图  6.7 显示了此时总线和 PCI-PCI 桥接器的编号方式。


图 6.8:配置 PCI 系统:第 3 部分

PCI-PCI 桥接器编号:步骤 3
PCI 初始化代码返回扫描 PCI 总线 1,并找到另一个 PCI-PCI 桥接器,桥接器3。它被分配 1 作为其主总线接口号,3 作为其辅助总线接口号,0xFF 作为其从属总线号。图  6.8 第 pageref 页显示了现在的系统配置方式。总线号为 1、2 或 3 的类型 1 PCI 配置周期将正确地传递到相应的 PCI 总线。


图 6.9:配置 PCI 系统:第 4 部分

PCI-PCI 桥接器编号:步骤 4
Linux 开始扫描 PCI 总线 3,这是 PCI-PCI 桥接器3 的下游总线。PCI 总线 3 上有另一个 PCI-PCI 桥接器(桥接器4),它被分配 3 作为其主总线号,4 作为其辅助总线号。它是此分支上的最后一个桥接器,因此为其分配从属总线接口号 4。初始化代码返回到 PCI-PCI 桥接器3,并为其分配从属总线号 4。最后,PCI 初始化代码可以为 PCI-PCI 桥接器1 分配从属总线号 4。图  6.9 第 pageref 页显示了最终的总线号。

6.6.3  PCI BIOS 功能

PCI BIOS 功能是一系列跨所有平台的标准例程。例如,它们对于基于 Intel 和 Alpha AXP 的系统都是相同的。它们允许 CPU 控制访问所有 PCI 地址空间。

只有 Linux 内核代码和设备驱动程序可以使用它们。

6.6.4  PCI 修复

Alpha AXP 的 PCI 修复代码比 Intel 的(基本上什么都不做)做得更多。

对于基于 Intel 的系统,在启动时运行的系统 BIOS 已经完全配置了 PCI 系统。这使得 Linux 除了映射该配置之外几乎无事可做。对于非基于 Intel 的系统,还需要进行进一步的配置,以

接下来的小节描述了该代码的工作原理。

了解设备需要多少 PCI I/O 和 PCI 内存空间

查询找到的每个 PCI 设备,以了解它需要多少 PCI I/O 和 PCI 内存地址空间。为此,将所有 1 都写入每个基地址寄存器,然后读取它。设备将在无关地址位中返回 0,从而有效地指定所需的地址空间。


图 6.10:PCI 配置标头:基地址寄存器

基地址寄存器有两种基本类型,第一种类型指示设备寄存器必须驻留在哪个地址空间中;PCI I/O 或 PCI 内存空间。这由寄存器的位 0 指示。图  6.10 显示了 PCI 内存和 PCI I/O 的基地址寄存器的两种形式。

为了了解给定的基地址寄存器正在请求多少地址空间,您需要将所有 1 写入寄存器,然后将其读回。设备将在无关地址位中指定零,从而有效地指定所需的地址空间。这种设计意味着所有使用的地址空间都是 2 的幂,并且自然对齐。

例如,当您初始化 DECChip 21142 PCI 快速以太网设备时,它会告诉您它需要 0x100 字节的 PCI I/O 或 PCI 内存空间。初始化代码为其分配空间。分配空间后,可以在这些地址看到 21142 的控制和状态寄存器。

为 PCI-PCI 桥接器和设备分配 PCI I/O 和 PCI 内存

与所有内存一样,PCI I/O 和 PCI 内存空间是有限的,并且在某种程度上是稀缺的。非 Intel 系统的 PCI 修复代码(以及 Intel 系统的 BIOS 代码)必须以有效的方式为每个设备分配它请求的内存量。PCI I/O 和 PCI 内存都必须以自然对齐的方式分配给设备。例如,如果设备请求 0xB0 的 PCI I/O 空间,则它必须与 0xB0 的倍数的地址对齐。除此之外,任何给定桥接器的 PCI I/O 和 PCI 内存基址必须分别与 4K 和 1Mbyte 边界对齐。鉴于下游设备的地址空间必须位于任何给定上游 PCI-PCI 桥接器的所有内存范围内,因此有效地分配空间是一个有些困难的问题。

Linux 使用的算法依赖于由 PCI 设备驱动程序构建的总线/设备树描述的每个设备都以升序 PCI I/O 内存顺序分配地址空间。再次使用递归算法来遍历 PCI 初始化代码构建的pci_buspci_dev数据结构。从根 PCI 总线(由pci_root指向)开始,BIOS 修复代码

以图  6.1 第 pageref 页中的 PCI 系统为例,PCI 修复代码将按以下方式设置系统

对齐 PCI 基址
PCI I/O 地址为 0x4000,PCI 内存地址为 0x100000。这使得 PCI-ISA 桥接器能够将低于这些地址的所有地址转换为 ISA 地址周期,
视频设备
这里需要 0x200000 的 PCI 内存,因此我们从当前的 PCI 内存基地址 0x200000 开始分配这个大小的内存,因为它必须自然对齐到请求的大小。PCI 内存基地址移动到 0x400000,PCI I/O 基地址仍然保持在 0x4000
PCI-PCI 桥接器
现在我们跨越 PCI-PCI 桥接器并在那里分配 PCI 内存,注意我们不需要对齐基地址,因为它们已经正确对齐
以太网设备
这里需要 0xB0 字节的 PCI I/O 和 PCI 内存空间。它被分配了 PCI I/O 地址 0x4000 和 PCI 内存地址 0x400000。PCI 内存基地址移动到 0x4000B0,PCI I/O 基地址移动到 0x40B0
SCSI 设备
这里需要 0x1000 PCI 内存,因此在自然对齐后,它被分配到 0x401000。PCI I/O 基地址仍然是 0x40B0,PCI 内存基地址已移动到 0x402000
PCI-PCI 桥接器的 PCI I/O 和内存窗口
现在我们回到桥接器,并将其 PCI I/O 窗口设置为 0x40000x40B0 之间,以及将其 PCI 内存窗口设置为 0x4000000x402000 之间。这意味着 PCI-PCI 桥接器将忽略视频设备的 PCI 内存访问,如果它们是针对以太网或 SCSI 设备的,则将它们传递下去。


脚注

1 例如?


文件由 TTH 1.0 版本从 TEX 翻译而来。
章节顶部, 目录, 显示框架, 无框架
© 1996-1999 David A Rusling 版权声明