3.2 PCI子系统对象

在PCI总线体系结构中,存在两个核心的概念:PCI总线和PCI设备(桥设备是一种特殊的PCI设备)。基于对这两个核心概念的抽象,Linux PCI子系统定义了两个关键的数据结构:pci_bus和pci_dev,以分别描述PCI总线和PCI设备。

图3-9反映了PCI子系统对象之间的关系。接下来会看到,PCI总线的扫描、PCI设备的配置以及在sysfs文件系统中的目录树都是基于这种关系展开的。

在图3-9中,我们看到,总线分为两类:根总线和非根总线。系统中可能有多条根总线,它们组成一个链表。非根总线也链接到父总线的子总线链表中。每条总线都有一个设备链表,其中链接有桥设备和非桥设备。非根总线和引出它的桥设备相互关联起来。

图3-9 PCI子系统对象之间的关系

3.2.1 pci_bus:PCI总线

pci_bus是描述PCI总线的结构。无论是根PCI总线,还是非根PCI总线,都对应一个pci_bus(其结构中的域如表3-1所示)描述符。这里所述的PCI总线需要稍微解释一下。它并不是驱动模型中讲到的总线类型的概念。在PCI子系统中,和驱动模型相对应的概念是PCI总线类型,亦即前面提到的pci_bus_type。

表3-1 pci_bus结构中的域(来自文件include/linux/pci.h)

每条PCI非根总线的parent域都指向它在层次结构上的父总线,至于PCI根总线,因为没有更上层的总线,所以parent域为NULL。

对于PCI根总线而言,其pci_bus结构链入到根总线链表,根总线链表的表头记录在全局变量pci_root_buses,PCI根总线链入此链表的连接件为node域。

而对于非根PCI总线,其pci_bus结构通过node成员链接到父PCI总线的子总线链表children中。

每条PCI总线都维护一个自己的设备链表视图,以便描述所有连接在该PCI总线上的设备,其表头为devices域。

非根PCI总线的self域指向引出它的桥设备。而对于根PCI总线,虽然它是从主桥引出的,但是主桥的行为与PCI设备和桥设备有很大的不同,在PCI子系统中没有对应的pci_dev结构,因此这个域为NULL。

接下来介绍结构中描述PCI总线信息的主要域。primary、secondary和subordinate域反映了PCI总线的各种总线号。PCI总线枚举过程中,这些号码被确定下来,一方面记录在对应PCI桥设备的配置空间中(使得PCI配置事务可以正常工作),另一方面保存在pci_bus描述符的内部域中(被PCI核心提供给PCI设备驱动的API使用,或者被PCI设备驱动直接使用)。number域为PCI的总线号,总是等于secondary域的值。

3.2.2 pci_dev:PCI设备

PCI设备可以从两个方面理解。由于每条PCI总线上可以挂载的物理设备数目是有限的,因此作为扩展,每个物理设备可以包含多个功能,即逻辑设备。从物理硬件的角度,PCI设备实际上指的是包含从一个到八个“功能”的某个PCI硬件,它被集成到系统主板上,或者安装在插槽中,这是PCI物理硬件的概念。但是,从操作系统的角度,PCI设备的每个功能都是一个设备,或者说PCI逻辑设备,它们会在系统中有自己的数据结构,可以被内核或驱动独立地操作。

为避免混淆,本书用通常用术语“PCI插槽”来表示一个PCI物理设备,而用“PCI设备”作为PCI逻辑设备的简称,表示PCI物理设备中的一个功能。所有种类的PCI设备都可以用数据结构pci_dev(其结构中的域如表3-2所示)来描述。更为准确地说,一个pci_dev描述了PCI设备的一个功能,即PCI逻辑设备。

表3-2 pci_dev结构中的域

续表

续表

这里需要再强调一次,Linux PCI子系统中的pci_dev描述符指的是PCI逻辑设备。因此看到一个PCI主机适配器,就应该想到在系统内存中可能有多个对应的pci_dev。对于SCSI子系统也是这样的道理,只不过在那里,PCI设备功能的概念被换成了SCSI磁盘的逻辑单元。

pci_dev数据结构中一些域的具体说明如下。

每一个PCI设备也通过其pci_dev结构中的bus_list域链入其所在PCI总线的局部设备链表中,而对应的表头就在前面提到的pci_bus结构中的devices域。

指针bus指向这个PCI设备所桥接的主总线,而指针subordinate指向这个PCI设备所桥接的次总线。后者仅对桥设备才有意义,对于一般的非桥PCI设备而言,subordinate指针域总是为NULL。

PCI设备的devfn域是PCI设备在PCI总线上的编号,是将高5位为插槽号、低3位为功能号放在一起编码的结果,它们是在PCI总线扫描过程中配置的。

pci_dev结构中有很多域是PCI设备的配置空间内容在内存中的“副本”,它们在PCI子系统初始化过程中被读出,保存在内存中。内核和驱动对PCI的操作大体上直接在这些域上进行,除非发生修改,需要将修改后的数据更新到PCI设备的配置空间。

hdr_type域对应从PCI设备配置寄存器中读取的头类型的低7位,第8位多功能标志已经被屏蔽。值00h表示描述符描述的是一个标准的PCI设备,值01h描述的是PCI-PCI桥设备。multifunction域则记录了PCI设备配置寄存器中的头类型域的高1位,即上面被被屏蔽掉的多功能位,表示这个PCI逻辑设备是否是多功能设备的一部分。

虽然标准PCI设备和PCI-PCI桥设备的配置空间中都含有ROM基地址寄存器,但是ROM基地址寄存器在这两种不同类型的PCI配置空间头部的位置是不一样的,对于类型0的配置空间布局,ROM基地址寄存器的起始位置是30h,而对于PCI-to-PCI桥所用的类型1配置空间布局,ROM基地址寄存器的起始位置是38h。rom_base_reg域记录下ROM基地址寄存器在PCI配置空间中的位置。

无论是配置,还是扫描,都是基于PCI逻辑设备的。PCI物理设备的概念在PCI子系统中并不那么突出,也不是本书的重点。不过它也不可或缺,一个典型的例子就是热插拔,这必然要针对PCI物理设备。描述PCI物理设备(或称物理插槽)的数据结构是pci_slot(其结构中的域如表3-3所示),pci_dev描述符的slot域就指向它。

表3-3 pci_slot结构中的域(来自文件include/linux/pci.h)

一条PCI总线包含多个PCI插槽,每个PCI插槽属于一条PCI总线。PCI插槽通过bus域指向所属的PCI总线,并以list域为连接件链接到它的PCI插槽链表,PCI总线描述符的slots域为该链表的表头。当然,判断PCI设备和PCI插槽是否有关联,需要看它们的插槽/功能号的高5位是否一样。

在讨论了与业务逻辑有关的域之后,在回过头看与Linux驱动模型有关的域。PCI总线和PCI设备都分别有一个内嵌的驱动模型设备描述符,不过它们的作用不同。PCI总线内嵌的是一个类设备对象,通过它链接到PCI总线类(pcibus_class)的设备链表;而PCI设备内嵌的是一个通用设备对象,通过它链接到PCI总线类型(pci_bus_type)的设备链表。

此外,在PCI总线描述符中,还有一个指针域bridge,对于非根PCI总线,它指向引出这条总线的桥设备内嵌的驱动模型设备,这样就在sysfs文件系统中建立了PCI总线和PCI设备之间的联系,也即确定了它们所对应目录的相对位置,PCI子系统的驱动模型对象关系如图3-10所示。对于根PCI总线,这个域指向一个新建的“虚拟”驱动模型设备,我们可以将它看作是主桥在sysfs文件系统中的代表——它是这条总线及所有下游总线和设备所对应目录树在sysfs文件系统中的根。

图3-10 PCI子系统的驱动模型对象关系