Peripheral Component
Interconnect(PCI),似乎它的名字暗示的一样,是描述如何通过一个结构化和可控制的方式把系统中的外设组件连接起来的一个标准。标准的PCI Local
Bus规范描述了系统组件电气连接的方法和它们行为的方法。本章探讨Linux核心如何初始化系统的PCI总线和设备。
图6.1是一个PCI基础的系统的逻辑图。PCI总线和PCI-PCI桥(bridge)是系统组件联系在一起的粘合剂。CUP和video设备连在主要的PCI总线,PCI总线0。一个非凡的PCI设备,PCI-PCI桥把主总线连接到次PCI总线,PCI总线1。按照PCI规范的术语,PCI总线1描述成为PCI-PCI桥的下游而PCI总线0是桥的上游。连接在次PCI总线上的是系统的SCSI和以太网设备。物理上桥、次要PCI总线和这两种设备可以在同一块PCI卡上。系统中的PCI-ISA桥支持老的、遗留的ISA设备,本图显示了一个超级I/O控制芯片,控制键盘、鼠标和软驱。
6.1 PCI Address Space(PCI地址空间)
CPU和PCI设备需要访问它们所共享的内存。这些内存让设备驱动程序控制这些PCI设备并在它们之间传递信息。一般地共享的内存包括设备的控制和状态寄存器。这些寄存器用于控制设备和读取它的状态。例如:PCI SCSI设备驱动程序可以读取SCSI设备的状态寄存器,判定它是否可以向SCSI磁盘写一块信息。或者它可以写入控制寄存器让它关闭的设备开始运行。
CPU的使用的系统内存可以用作这种共享内存,但是假如这样的话,每一次PCI设备访问内存,CPU都不得不停顿,等待PCI设备完成。对于内存的访问通常有限制,同一时间只能有一个系统组件答应访问。这会使得系统速度降低。答应系统的外部设备在一个不受控的方式下访问主内存也不是一个好主意。这会非常危险:一个恶意的设备会让系统非常不稳定。
外部设备由它们自己的内存空间。CPU可以访问这些空间,但是设备对于系统内存的访问受到严格的控制,必须通过DMA(Direct Memory Access直接内存存取)通道。ISA设备可以访问两种地址空间:ISA I/O(输入/输出)和ISA内存。PCI由三中:PCI I/O、PCI内存和PCI配置空间(configuration space)。CPU可以访问所有的地址空间其中PCI I/O和PCI内存地址空间由设备驱动程序使用而PCI配置空间由Linux和心中的PCI初始化代码使用。
Alpha AXP处理器没有对于除了系统地址空间之外的地址空间的天生的访问模式。它需要使用支持芯片来访问象PCI配置空间这样的其他地址空间。它使用了一个地址空间的映射方案,从巨大的虚拟地址空间中偷出一部分映射到PCI地址空间。
6.2 PCI Configuration Headers(PCI配置头)
系统中的每一个PCI设备,包括PCI-PCI桥都由一个配置数据结构,位于PCI配置地址空间中。PCI配置头答应系统识别和控制设备。这个头位于PCI配置地址空间的确切位置依靠于设备使用的PCI拓扑。例如,插在PC主板一个PCI槽位的一个PCI显示卡配置头会在一个位置,而假如它被插到另一个PCI槽位则它的头会出现在PCI配置内存中的另一个位置。但是不管这些PCI设备和桥在什么位置,系统都可以发现并使用它们配置头中的状态和配置寄存器来配置它们。
通常,系统的设计使得每一个PCI槽位的PCI配置头都有一个和它在板上的槽位相关的偏移量。所以,举例来说,板上的第一个槽位的PCI配置可能位于偏移0而第二个槽位的在偏移256(所有的头都一样长度,256字节),依此类推。定义了系统相关的硬件机制使得PCI配置代码可以尝试检查一个给定的PCI总线上的所有可能的PCI配置头,试图读取头中的一个域(通常是Vendor Identification 域)得到一些错误,从而知道那些设备存在而那些设备不存在。PCI Local Bus规范描述了一种可能的错误信息:试图读取一个空的PCI槽位的Verdor Identification和Device Indentification域时候返回0xFFFFFFFF。
图6.2显示了256字节的PCI配置头的布局。它包括以下域:
参见include/linux/pci.h
Vendor Identification 唯一的数字,描述这个PCI设备的发明者。Digital的PCI Vendor Identification 是0x1011而Intel是0x8086。
Device Identification 描述设备自身的唯一数字。例如Digital的21141快速以太网设备的设备标识符是0x0009。
Status 此域给除了设备的状态,它的位的含义由PCI Local Bus规范规定。
Command 系统通过写这个域控制这个设备。例如:答应设备访问PCI I/O内存。
Class Code 标识了设备的类型。对于每一种设备都有标准分类:显示、SCSI等等。对于SCSI的类型编码是0x0100。
Base Address Registers 这些寄存器用于确定和分配设备可以使用的PCI I/O和PCI内存的类型、大小和位置。
Interrupt Pin PCI卡的物理管脚中的4个用于向PCI总线传递中断。标准中把它们标记为A、B、C和D。Interrupt Pin域描述了这个PCI设备使用那个管脚。通常对于一个设备来说这时硬件决定的。就是说每一次系统启动的时候,这个设备都使用同一个中断管脚。这些信息答应中断处理子系统治理这些设备的中断。
Interrupt Line PCI配置头中的Interrupt Line域用于在PCI初始化代码、设备驱动程序和Linux的中断处理子系统之间传递中断控制。写在这里的数字对于设备驱动程序来讲是没有意义的,但是它可以让中断处理程序正确地把一个中断从PCI设备发送到Linux操作系统中正确的设备驱动程序的中断处理代码处。Linux如何处理中断参看第7章。
6.3 PCI I/O and PCI Memory Address(PCI I/O和PCI内存地址)
这两种地址空间用于设备和CPU上运行的Linux核心的它们的设备驱动程序通讯。例如:DECchip 21141快速以太网设备把它的内部寄存器映射到了PCI I/O空间。然后它的Linux设备驱动程序通过读写这些寄存器来控制设备。显示驱动程序通常使用大量的PCI内存空间来放置显示信息。
直到PCI系统建立起来并使用PCI配置头中的Command域打开了设备对于这些地址空间的访问为止,设备都无法访问这些空间。应该注重的是只有PCI配置代码读写PCI配置地址,Linux的设备驱动程序只是读写PCI I/O和PCI内存地址。
6.4 PCI-
ISA Bridges(PCI-ISA桥)
这种桥把对于PCI I/O和PCI内存地址空间的访问转换成为ISA I/O和ISA内存访问,用来支持ISA设备。现在销售的多数系统都包括几个ISA总线插槽和几个PCI总线插槽。这种向后的兼容的需要会不断减少,将来会有只有PCI的系统。在早期的Intel 8080基础的PC时代,系统中的ISA设备的ISA 地址空间(I/O和内存)就被固定下来。甚至一个S5000 Alpha AXP基础的计算机系统的ISA软驱驱动器的ISA I/O地址也会和第一台IBM PC一样。PCI规范保留了PCI I/O和PCI内存的地址空间中的较低的区域保留给系统中的ISA外设并使用一个PCI-ISA桥把所有对于这些区域的PCI内存访问转换为ISA访问。
6.5 PCI-PCI Bridges(PCI-PCI桥)
PCI-PCI桥是非凡的PCI设备,把系统中的PCI总线粘和在一起。简单系统中只有一个PCI总线,当时单个PCI总线可以支持的PCI设备的数量有电气限制。使用PCI-PCI桥增加更多的PCI总线答应系统支持更多的PCI设备。这对于高性能的服务器尤其重要。当然,Linux完全支持使用PCI-PCI桥的使用。
6.5.1 PCI-PCI Bridges: PCI I/O and PCI Memory Windows
PCI-PCI桥只向下游传递对于PCI I/O和PCI内存读和写的一个子集。例如在图6.1中,只有读和写的地址属于SCSI或者以太网设备的时候PCI-PCI桥才会把读写的地址从PCI总线0传递到总线1,其余的都被忽略。这种过滤阻止了不必要的地址信息遍历系统。为了达到这个目的,PCI-PCI桥必须编程设置它们必须从主总线向次总线通过的PCI I/O和PCI内存地址空间访问的基础(base)和限制。一旦系统中的PCI-PCI桥设置好,只要Linux设备驱动程序只是通过这些窗口存取PCI I/O和PCI内存空间,PCI-PCI桥是不可见的。这是个重要的特性,使得Linux的PCI设备驱动程序的作者的日子好过了。但是它也让Linux下的PCI-PCI桥在一定程度上需要技巧才能配置,我们不久就会看到。
6.5.2 PCI-PCI Bridges: PCI Configuration Cycles and PCI Bus Numbering(PCI-PCI桥:PCI配置cycle和PCI总线编号)
既然CPU的PCI初始化代码可以定位不在主PCI总线上的设备,必须有一种机制使得桥可以决定是否把配置cycle从它的主接口传递到次接口上。一个cycle就是它显示在PCI总线上的地址。PCI规范定义了两种PCI地址配置格式:类型0和类型1,分别在图6.3和图6.4中显示。类型0的PCI配置cycle不包含总线号,被这个PCI总线上的所有的PCI设备解释用于PCI地址配置。配置cycle的位32:11看作是设备选择域。设计系统的一个方法是让每一个位选择一个不同的设备。这种情况下为11可能选择槽位0的PCI设备,位12选择槽位1的PCI设备,依此类推。另一种方法是把设备的槽位号直接写到位31:11中。一个系统使用哪一种机制依靠于系统的PCI内存控制器。
类型1的PCI配置cycle包括一个PCI总线号,这种配置循环被除了PCI-PCI桥之外的所有PCI设备忽略。所有看到了类型1的PCI配置cycle的PCI-PCI桥都可以把这些信息向它们的下游传送。一个PCI-PCI桥是否忽略PCI配置循环或者向它的下游传递,依靠于这个桥是如何配置的。每一个PCI-PCI桥都有一个主总线接口号和一个次总线接口号。主总线接口离CPU最近而次总线接口是离CPU最远的。每一个PCI-PCI桥都还有一个附属总线编号,这是在第二个总线接口之外可以桥接的最大的PCI总线数目。或者说,附属总线编号是PCI-PCI桥下游的最大的PCI总线编号。当PCI-PCI桥看到一个类型1的PCI配置cycle的时候,它做以下事情:
假如指定的总线编号不在桥的次总线编号和总线的附属编号之间就忽略它。
假如指定的总线编号和桥的次总线编号符合就把它转变成为类型0的配置命令
假如指定的总线编号大于次要总线编号而小于或等于附属总线编号,就不改变地传递到次要总线接口上。
所以,假如我们希望寻址图6.9的拓扑中总线3上的设备1,我们必须从CPU生成一个类型1的配置命令。桥1不改变地传递到总线1,桥2忽略它但是桥3把它转换成一个类型0的配置命令,并把它发送到总线3,使设备1响应它。
每一个独立的操作系统负责在PCI配置阶段分配总线编号,但是不管使用哪一种编码方案,对于系统中所有的PCI-PCI桥,以下陈述都必须是正确的:
所有位于一个PCI-PCI桥后面的PCI总线的编码都必须在次总线编号和附属总线编号之间(包含)
假如违反了这条规则,则PCI-PCI桥将无法正确地传递和转换类型1的PCI配置cycle,系统无法成功地找到并初始化系统中的PCI设备。为了完成编码方案,Linux按照特定的顺序配置这些非凡设备。参看6.6.2节对于Linux PCI桥和总线编码方案的描述以及一个可以工作的例子。
6.6 Linux PCI Initialization(Linux PCI初始化过程)
Linux中PCI初始化代码分为三个逻辑部分:
PCI Device Driver 这个伪设备驱动程序从总线0开始查找PCI系统,定位系统中所有的PCI设备和桥。它建立一链接的数据结构的列表,描述系统的拓扑。另外,它还为系统中所有的桥编码。
参见drivers/pci/pci.c and include/linux/pci.h
PCI BIOS 这个软件层提供了PCI BIOS ROM规范中描述的服务。即使Alpha AXP没有BIOS服务,在Linux核心也有提供了相同的功能的等价代码。
参见arch/*/kernel/bios32.c
PCI Fixup 系统相关的整理代码,整理和系统相关的在PCI初始化最后的内存疏松的情况。
参见arch/*/kernel/bios32.c
6.6.1 Linux Kernel PCI Data Structures(Linux核心的PCI数据结构)
当Linux核心初始化PCI系统的时候它建立反映系统真实的PCI拓扑结构的数据结构。图6.5显示了数据结构之间的关系,它用来描述了图6.1中示例的PCI系统。
每一个PCI设备(包括PCI-PCI桥)都用一个pci_dev的数据结构描述。每一个PCI总线用一个pci_bus的数据结构描述。结果是一个PCI总线的树型结构,每一个总线上有粘附着一些子PCI设备。因为一个PCI总线只能通过PCI-PCI桥达到(除了主PCI总线,总线0),每一个pci_bus都包括一个它要通过的PCI设备的指针(这个PCI-PCI桥)。这个PCI设备是这个PCI总线的父总线的一个子设备。
图6.5中
没有显示的还有一个指向系统中所有的PCI设备的指针:pci_devices。系统中所有的PCI设备的pci_dev的数据结构都排在这个队列中。Linux核心使用这个队列快速查找系统中所有的PCI设备。
6.6.2 The PCI Device Driver(PCI 设备驱动程序)
PCI设备驱动程序完全不是一个真正的设备驱动程序,只是系统初始化的时候操作系统调用的一个函数。PCI初始化代码必须扫描系统中所有的PCI总线,查找系统中所有的PCI设备(包括PCI-PCI桥接设备)。它使用PCI BIOS代码来查看它当前扫描的PCI总线上的每一个可能的槽位是否被占用。假如这个PCI槽位占用,它就建立一个描述这个设备的pci_dev数据结构,并把它链接到已知PCI设备的列表中(由pci_deivices指向)。
参见drivers/pci/pci.c Scan_bus()
PCI初始化代码从PCI总线0开始扫描。它试图读出每一个可能的PCI槽位中每一个可能的PCI设备的Vendor Identification和Device Identification域。当它找到了占用的槽位它就建立一个pci_dev数据结构来描述它。PCI初始化代码所建立的所有的pci_dev数据结构(包括所有的PCI-PCI桥)都链接到一个链接表:pci_devices。
假如找到的设备是一个PCI-PCI桥,则建立一个pci_bus的数据结构,并链接到pci_root指向的由pci_bus和pci_dev数据结构组成的树上。PCI的初始代码可以判定PCI设备是否PCI-PCI桥,因为它的分类编码(class code)是0x060400。然后Linux核心配置它刚刚找到的PCI-PCI桥的另一端的PCI总线(下游)。假如找到更多的PCI-PCI桥,它们都一样被配置。这个过程成为深度(depthwize)算法:系统在宽度搜索之前先在深度展开。看图6.1,Linux会首先配置PCI总线1和它的以太网和SCSI设备,然后配置PCI总线0上的显示设备。
在Linux向下游查找PCI总线的时候它必须配置介入的PCI-PCI桥的次总线和附属总线编号。这些在下面的6.6.2节具体描述:
Configuring PCI-PCI Bridges – Assigning PCI Bus Numbers(配置PCI-PCI桥-分配PCI总线编号)
对于传送通过它们进行的PCI I/O、PCI内存或者PCI配置地址空间的读写,PCI-PCI桥必须直到以下:
Primary Bus Number 刚好在PCI-PCI桥上游的总线编号
Secondary Bus Number 刚好在PCI-PCI桥下游的总线编号
Subordinate Bus Number 从这个桥向下可以达到的所有总线中最高的总线编号。
PCI I/O and PCI Memory Windows 从这个PCI-PCI桥向下的所有的地址的PCI I/O地址空间和PCI 内存空间的窗口的base和size。
问题是当你希望配置任何指定的PCI-PCI桥的时候你并不知道这个桥的附属总线数目。你不知道是否下游还有其他PCI-PCI桥。就算知道,你也不知道它们将会被分配什么编号。答案是使用一个深度递归算法(depthwise recursive algorithm)。在每一个总线上找到任何PCI-PCI桥的时候都就给它们分配编号。对于找到的每一个PCI-PCI桥,就给它的次总线分配编号,并给它分配临时的附属总线编号0xFF,并扫描它的下游所有的PCI-PCI桥并分配编号。这看起来相当复杂,但是下面的实际例子能使这个过程更清楚。
PCI-PCI Bridge Numbering: Step 1 参考图6.6中的拓扑,扫描找到的第一个桥是桥1(Bridge1)。桥1下游的PCI总线编号为1,桥1分配一个次总线号1和一个临时的附属总线编号0xFF。这意味着指定PCI总线1或更高的所用的类型1的PCI配置地址会穿过桥1到达PCI总线1。假如它们的总线编号是1,就转换成为类型0的配置cycle,否则对于其他的总线编号就不变。这也正是Linux PCI初始化代码需要做的,这样才能访问并扫描PCI总线1。
PCI-PCI Bridge Numbering: Step 2 Linux使用深度算法,所以初始化代码开始扫描PCI总线1。这是它找到了PCI-PCI桥2,桥2之外没有其他的PCI-PCI桥,所以它的附属总线编号成为2,和它的次接口一样。图6.7显示了总线和PCI-PCI桥这时是如何编码的。
PCI-PCI Bridge Numbering:Step 3 PCI初始化代码回来扫描PCI总线1,找到了另一个PCI-PCI桥3。它的主总线接口赋值1而它的次总线接口是3,它的附属总线编号是0xFF。图6.8显示了系统这时是如何配置的。带有总线编号1、2或3的类型1的PCI配置cycle现在可以正确地传送到适当的PCI总线。
6.6.3 PCI BIOS Functions(PCI BIOS函数)
PCI BIOS函数是通用的跨平台的一系列标准例程。例如,它们对于Intel和Alpha AXP系统都是一样的。它们答应CPU控制对于所有PCI地址空间的访问。只有Linux核心和设备驱动程序需要使用它们。
参见arch/*/kernel/bios32.c
6.6.4 PCI Fixup
Alpha AXP系统上的PCI整理代码比Intel(基本不做任何事情)要做更多的工作。对于Intel系统,启动时候运行的系统BIOS,已经完全配置了PCI系统。Linux不需要做更多的事情,只是映射PCI的配置。对于非Intel系统,需要做更多的配置:
参见arch/kernel/bios32.c
对于每一个设备分配PCI I/O和PCI内存空间
对于系统重的每一个PCI-PCI桥必须配置PCI I/O和PCI内存地址窗口
对于设备产生Interrupt Line值,这些控制设备的中断处理
下面描述这些代码如何工作。
Finding Out How Much PCI I/O and PCI Memory Space a Device Needs
(找出一个设备需要多少PCI I/O和PCI内存空间)
查询找到的每一个PCI设备,找出它需要多少PCI I/O和内存地址空间。为此,把每一个Base Address Register都写成1然后读出来。设备会在不关心的地址位返回1,有效地指定了需要的地址空间。
用两个基本的基础地址寄存器(Base Address Register),第一种指示设备的寄存器以及PCI I/O和PCI 内存空间必须在哪一个地址空间。这通过寄存器的0位表示。图6.10显示了PCI 内存和PCI I/O的基础地址寄存器的两种形式。
为了找出每一个给定的基础地址寄存器需要多少地址空间,需要向所有的寄存器写并读出来。设备会把不关心的地址位设为0,这样就有效地指明了需要的地址空间。这种设计暗示了使用的所有的地址空间都是2的指数,本质上是对齐的。
例如,在你初始化DECChip 21142 PCI快速以太网设备的时候,它告诉你在PCI I/O或PCI内存空间它需要0x100字节的地址。初始化代码为它分配空间。在它分配空间之后,21142的控制和状态寄存器就可以在这些地址见到。
Allocating PCI I/O and PCI Memory to PCI-PCI Bridges and Devices
(为PCI-PCI桥和设备分配PCI I/O和PCI内存)
象所有的内存一样,PCI I/O和PCI内存空间是有限的,其中有一些相当紧缺。对于非Intel系统的PCI整理代码(和Intel系统的BIOS代码)必须有效地为每一个设备分配它需要的内存量。分配给一个设备的PCI I/O和PCI内存的分配必须自然对齐。例如,假如一个设备请求PCI I/O地址0xB0,那么分配的地址就必须是0xB0的倍数。另外,分配给任何桥的PCI I/O和PCI内存地址的基础必须分别对齐4K和1M的边界。下游的设备给定的地址空间必须位于它所有的上游的PCI-PCI桥的内存范围中间。所以有效地分配地址空间是有比较困难的问题。
Linux使用的算法依靠于用PCI设备驱动程序建立的总线/设备树所描述的每一个设备,它按照PCI I/O内存递增的顺序分配地址空间。又是使用递归算法,遍历PCI初始化代码所建立的pci_bus和pci_dev数据结构。BIOS整理代码从PCI总线的根(pci_root所指)开始:
分别把当前的全局PCI I/O和内存的基础分别对齐在4K和1M的边界
对于当前总线上的每一个设备(按照需要的PCI I/O内存顺序排列)
-分配它的PCI I/O和/或PCI内存
-将全局的PCI I/O和内存的基础按照合适的量移动
-答应设备使用给定的PCI I/O和PCI内存
分别为当前总线下游的所有总线分配空间,注重这会改变全局的PCI I/O和内存基础。
分别把当前的全局PCI I/O和内存的基础对齐在4K和1M的边界,同时指出当前的PCI-PCI桥所需要的PCI I/O和PCI内存的窗口的基础和大小
对于连接在当前总线上的PCI-PCI桥,设置它的PCI-PCI I/O和PCI内存地址和限制。
打开PCI-PCI桥上桥接PCI I/O和PCI内存访问的功能。这意味着假如任何在桥的主PCI总线上看到的PCI I/O和PCI内存地址假如位于它的PCI I/O和PCI内存地址窗口的话就会被桥接到它的次总线。
以图6.1的PCI系统作为PCI整理代码的例子:
Align the PCI base (初始的)PCI I/O是0x4000,PCI内存是0x100000。这样答应PCI-ISA桥把所有低于此的地址都转换到ISA地址。
The Video Device 请求0x200000的PCI内存,因为必须按照要求的大小对齐,所以我们从PCI内存0x200000开始分配,PCI内存基础地址移到了0x400000,而PCI I/O地址仍然是0x4000。
The PCI-PCI Bridges 我们现在穿过PCI-PCI桥,在那里分配内存。注重因为我们不需要对其基础地址因为它们已经正确对齐了。
The Ethernet Device 它在PCI I/O和PCI内存空间都请求0xB0字节。它被分配在PCI I/O地址0x4000,PCI内存0x400000。PCI内存的基础移到了0x4000B0,PCI I/O的基础成为0x40B0。
The SCSI Device 它请求0x1000的PCI内存,所以它在对齐之后分配在0x401000。而PCI I/O的基础地址还是0x40B0,PCI内存的基础移到了0x402000。
The PCI-PCI Bridge’s PCI I/O and Memory Windows 我们现在返回到桥,把它的PCI I/O窗口设置成为0x4000和0x40B0之间,它的PCI内存窗口在0x400000和0x402000之间。这意味着PCI-PCI桥会忽略对于显示设备的PCI内存访问,假如是对以太网或者SCSI设备的访问就可以通过。
视频教程列表
文章教程搜索
C语言程序设计推荐教程
C语言程序设计热门教程
|