emmoblin.github.com
分类: LINUX
2008-11-13 19:54:04
图6.1是一个基于PCI的系统示意图。PCI总线和PCI-PCI桥接器在连接系统中设备到上起关键作用,在这个系统中CPU和视频设备被连到PCI bus 0上,它是系统中的主干PCI总线。而PCI-PCI桥接器这个特殊PCI设备将主干总线PCI bus 0与下级总线PCI bus 1连接到一起。PCI标准术语中,PCI bus 1是PCI-PCI桥接器的downstream而PCI bus 0是此桥接器的up-stream。SCSI和以太网设备通过二级PCI总线连接到这个系统中。而在物理实现上,桥接器和二级PCI总线被集成到一块PCI卡上。而PCI-ISA桥接器用来支持古老的ISA设备,图中有一个高级I/O控制芯片来控制键盘、鼠标及软盘设备。
而PCI设备需要访问三种地址空间:PCI I/O、PCI内存和PCI配置空间。CPU则可以访问所有这些地址空间。PCI I/O和 PCI内存由设备驱动程序使用而PCI配置空间被Linux 核心中的PCI初始化代码使用。
系统中每个PCI设备,包括PCI-PCI桥接器在内,都有一个配置数据结构,它通常位于PCI配置地址空间中。PCI配置头允许系统来标识与控制设备。配置头在PCI配置空间的位置取决于系统中PCI设备的拓扑结构。例如将一个PCI视频卡插入不同的PCI槽,其配置头位置会变化。但对系统没什么影响,系统将找到每个PCI设备与桥接器并使用它们配置头中的信息来配置其寄存器。
典型的办法是用PCI槽相对主板的位置来决定其PCI配置头在配置空间中的偏移。比如主板中的第一个PCI槽的PCI配置头位于配置空间偏移0处,而第二个则位于偏移256处(所有PCI配置头长度都相等,为256字节),其它槽可以由此类推。系统还将提供一种硬件相关机制以便PCI设置代码能正确的辨认出对应PCI总线上所有存在的设备的PCI配置头。通过PCI配置头中的某些域来判断哪些设备存在及哪些设备不存在(这个域叫厂商标志域: Vendor Identification field)。对空PCI槽中这个域的读操作将得到一个值为0xFFFFFFFF的错误信息。
图6.2给出了256字节PCI配置头的结构,它包含以下域:
这两个地址空间用来实现PCI设备和Linux核心中设备驱动程序之间的通讯。例如DEC21141快速以太网设备的内部寄存器被映射到PIC I/O空间上时,其对应的Linux设备驱动可以通过对这些寄存器的读写来控制此设备。PCI视频卡通常使用大量的PCI内存空间来存储视频信息。
在PCI系统建立并通过用PCI配置头中的命令域来打开这些地址空间前,系统决不允许对它们进行存取。值得注意的是只有PCI配置代码读取和写入PCI配置空间,Linux设备驱动只读写PCI I/O和PCI内存地址。 那是因为当系统初始化阶段完成后,每个PCI设备的地址空间都已经应设在PCI总线上了,驱动程序直接通过总线地址就可以访问PCI设备,当然也可以去读写配置空间,但这没有必要。
PCI-PCI桥接器是一种将系统中所有PCI总线连接起来的特殊PCI设备。在简单系统中只存在一条PCI总线,由于受电气特性的限制,它所连接的PCI设备个数有限。引入PCI-PCI桥接器后系统可以使用更多的PCI设备。对于高性能服务器这是非常重要的。Linux提供了对PCI-PCI桥接器的全面支持。
为了让CPU上运行的PCI初始化代码能访问位于分支PCI总线上的设备,必须为桥接器提供某种机制以便它可以决定是否将配置循环从主干接口传递到其二级接口。循环是出现在PCI总线上的一个地址。PCI 标准定义了两种PCI配置寻址格式;类型0和类型1;它们分别如图6.3及6.4所示。类型0用于pci设备,类型1用于桥设备。
类型0 PCI配置循环不包含总线序号,同时在此PCI总线上对应于这个PCI配置地址的所有PCI设备都会来对它们进行解释。类型0 配置循环的11 位到31位用来进行PCI设备选择。有种设计方式是让每位代表系统中一个不同的设备。这时11位对应PCI槽0中的PCI设备而12位标识槽1中的设备等等,如此类推。另外一种方式是直接将设备的槽号写入到位31到11中。系统使用哪种机制依赖于系统PCI内存控制器。
类型1 PCI配置循环包含一个PCI总线序号,同时这种配置循环将被除桥接器外的所有PCI设备所忽略。所有发现类型1 配置循环的PCI-PCI桥接器把它们看到的地址传递到各自的下级PCI总线。至于PCI-PCI桥接器是否忽略类型1 配置循环或将其传递到PCI总线则依赖于PCI-PCI桥接器的配置方式。每个PCI-PCI桥接器都拥有一个主干总线接口序号以及一个二级总线接口序号。主干总线是那个离CPU最近的PCI总线而二级总线是离它稍远的PCI总线。任何PCI-PCI桥接器还包含一个从属总线序号,这是所有二级总线接口所桥接的PCI总线中序号最大的那个。或者说这个从属总线序号是PCI-PCI桥接器向下连接中PCI总线的最大序号。当PCI-PCI桥接器看到类型1 PCI配置循环时它将进行如下操作:
所以如果想寻址PCI-PCI配置例4中总线3上的设备1,我们继续从CPU中产生一个类型1 配置命令。桥接器1将其传递给总线1。桥接器2虽然忽略它但会将其转换成一个类型0 配置命令并送到总线3上,在那里设备1将作出相应反应。
PCI配置中总线序号由操作系统来分配。但是序号分配策略必须遵循对系统中所有PCI-PCI桥接器都正确的描叙:
“位于PCI-PCI桥接器后所有的PCI总线必须位于二级总线序号和从属总线序号之间”。
如果这个规则被打破,则PCI-PCI桥接器将不能正确的传递与转换类型1 PCI配置循环,同时系统将找不到或者不能正确地初始化系统中的PCI设备。为了满足这个序号分配策略,Linux以特殊的顺序配置这些特殊的设备。一节详细描叙了Linux的PCI桥接器与总线序号分配策略。
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设备。
PCI设备驱动根本不是真正的设备驱动,它仅是在系统初始化时由操作系统调用的一些函数。PCI初始化代码将扫描系统中所有的PCI总线以找到系统中所有的PCI设备(包括PCI-PCI桥接器)。
它通过PCI BIOS代码来检查当前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_root指向的pci_dev结构和pci_bus树中。PCI初始化代码通过类别代码0x060400来判断此PCI设备是否是一个PCI-PCI桥接器。然后Linux 核心代码将配置此PCI-PCI桥接器下方的PCI设备。如果有更多的桥接器被找到则进行同样的配置。显然这个过程使用了深度优先搜索算法;系统中PCI拓扑将在进行广度映射前先进行深度优先映射。图6.1中Linux将在配置PCI总线0上的视频设备前先配置PCI设备1上的以太与SCSI设备。
由于Linux优先搜索从属的PCI总线,它必须处理PCI-PCI桥接器二级总线与从属总线序号。在下面的pci-pci总线序号分配中将进行详细讨论。
为了让PCI-PCI桥接器可以传递PCI I/O、PCI内存或PCI配置地址空间,它们需要如下内容:
配置任一PCI-PCI桥接器时我们对此桥接器的从属总线序号一无所知。不知道是否还有下一级桥接器存在,同时也不知道指派给它们的序号是什么。但可以使用深度优先遍历算法来对扫描出指定PCI-PCI桥接器连接的每条总线,同时将它们编号。当找到一个PCI-PCI桥接器时,其二级总线被编号并且将临时从属序号0xff指派给它以便对其所有下属PCI-PCI桥接器进行扫描与指定序号。以上过程看起来十分复杂,下面将提供一个实例以帮助理解。
PCI BIOS函数是一组适用于所有平台的标准过程。在Intel和Alpha AXP系统上没有区别。虽然在CPU控制下可以用它们对所有PCI地址空间进行访问。但只有Linux核心代码和设备驱动才能使用它们。
在Alpha AXP平台上的PCI补丁代码所作工作量要大于Intel平台。
基于Intel的系统在系统启动时就已经由系统BIOS完成了PCI系统的配置。Linux只需要完成简单的映射配置. 非Intel系统将需要更多的配置:
下一节将描叙这些代码的工作过程。
有两类基本的基地址寄存器,一类标识设备寄存器必须驻留的地址空间;另一类是PCI I/O或PCI内存空间。此寄存器的0位来进行类型的区分。图6.10给出了对应于PCI内存和PCI I/O两种不同类型的基地址寄存器。
确定某个基地址寄存器所需地址空间大小时,先向此寄存器写入全1再读取此寄存器,设备将在某些位填上0来形成一个二进制数表示所需有效地址空间。
以初始化DEC 21142 PCI快速以太设备为例,它将告诉系统需要0x100字节的PCI I/O空间或者PCI内存空间。于是初始化代码为其分配空间。空间分配完毕后,就可以在那些地址上看到21142的控制与状态寄存器。
Linux使用的算法依赖于由PCI设备驱动程序建立的描叙PCI设备的总线/设备树,每个设备的地址空间按照PCI I/O内存顺序的升序来分配。同时再次使用遍历算法来遍历由PCI初始化代码建立的pci_bus和 pci_dev结构。从根PCI总线开始(由pci_boot指向)PCI补丁代码将完成下列工作:
以图6.1中的PCI系统为例,PCI补丁代码将以如下方式设置系统: