§1.PCI总线体系结构概述
PCI总线体系结构是一种层次式的(Hierarchical)体系结构。在这种层次式体系结构中,PCI桥设备占据着重要的地位,他将父总线和子总线连接在一起,从而使整个系统看起来像一颗倒置的树型结构。树的顶端是系统的CPU,他通过一个较为特别的PCI桥设备??Host/PCI桥设备和根PCI总线(root pci bus)连接起来。下图1是个较为典型的PCI总线体系结构图。
(图1)
从上图也能看出,作为一种特别的PCI设备,PCI桥又包括以下几种:
(1)Host/PCI桥:用于连接CPU和PCI根总线(注意,the first root bus总是编号为0)。由于像Memory Controller这样的设备通常也被集成到Host/PCI桥设备芯片中,因此,Host/PCI桥通常也被称为“北桥芯片组(North Bridge Chipset)”。
(2)PCI/ISA桥:用于连接遗留的ISA总线。通常,像i8359A中断控制器这样的设备也会被集成到PCI/ISA桥设备中,因此,PCI/ISA桥通常也被称为“南桥芯片组(South Bridge Chipset)”。
(3)PCI-to-PCI桥:用于连接PCI主总线(primary bus)和次总线(secondary bus)。PCI桥所处的PCI总线称为“主总线”(即次总线的父总线),桥设备所连接的PCI总线称为“次总线”(即主总线的子总线)。
更多类型的PCI桥分类能参见《PCI Local Bus Specification》Revision2.2的附录D。
本文以下部分假设读者对PCI总线规范有一定的了解,因此本文是为那些想要深入了解Linux内核是怎么实现PCI总线驱动程式的Kernel Hacker而写的。
§2 PCI总线驱动程式的核心数据结构
从前面的图1能看出,在PCI总线体系结构中,有两个核心的概念存在:PCI总线和PCI设备(桥设备是一种特别的PCI设备)。基于对这两个核心概念的抽象,Linux PCI总线驱动程式定义了两个关键的数据结构:结构类型pci_bus和结构类型pci_dev,以分别描述pci总线和pci设备。在此基础上, Linux PCI总线驱动程式又将系统中当前存在的所有PCI总线的pci_bus结构组织成一张层次式的链表图,显然该链表图的顶层链表是系统中所有根总线的 pci_bus结构链表,因此用一个list_head结构类型的全局变量pci_root_buses来描述该链表图中的顶层链表表头,也即根总线链表的表头。系统中当前存在的所有PCI设备的pci_dev结构也被组织成一条链表,称为“全局pci设备链表”,用一个list_head结构类型的变量 pci_devices来表示该链表的表头。通过这两条总链表,Linux内核就能得到一张当前系统的PCI总线体系结构视图。
§2.1 PCI设备描述符??pci_dev结构类型
所有种类的PCI设备都能用结构类型pci_dev来描述。更为准确地说,应该是每一个PCI功能,即PCI逻辑设备都唯一地对应有一个pci_dev设备描述符。该数据结构的定义如下(include/linux/pci.h):
/*
* The pci_dev structure is used to describe both PCI and ISAPnP devices.
*/
struct pci_dev {
struct list_head global_list; /* node in list of all PCI devices */
struct list_head bus_list; /* node in per-bus list */
struct pci_bus *bus; /* bus this device is on */
struct pci_bus *subordinate; /* bus this device bridges to */
void *sysdata; /* hook for sys-specific extension */
struct proc_dir_entry *procent; /* device entry in /proc/bus/pci */
unsigned int devfn; /* encoded device & function index */
unsigned short vendor;
unsigned short device;
unsigned short subsystem_vendor;
unsigned short subsystem_device;
unsigned int class; /* 3 bytes: (base,sub,prog-if) */
u8 hdr_type; /* PCI header type (`multi’ flag masked out) */
u8 rom_base_reg; /* which config register controls the ROM */
struct pci_driver *driver; /* which driver has allocated this device */
void *driver_data; /* data private to the driver */
dma_addr_t dma_mask; /* Mask of the bits of bus address this
device implements. Normally this is
0xffffffff. You only need to change
this if your device has broken DMA
or supports 64-bit transfers. */
/* device is compatible with these IDs */
unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];
unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];
/*
* Instead of touching interrupt line and base address registers
* directly, use the values stored here. They might be different!
*/
unsigned int irq;
struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs */
struct resource dma_resource[DEVICE_COUNT_DMA];
struct resource irq_resource[DEVICE_COUNT_IRQ];
char name[80]; /* device name */
char slot_name[8]; /* slot name */
int active; /* ISAPnP: device is active */
int ro; /* ISAPnP: read only */
unsigned short regs; /* ISAPnP: supported registers */
int (*prepare)(struct pci_dev *dev); /* ISAPnP hooks */
int (*activate)(struct pci_dev *dev);
int (*deactivate)(struct pci_dev *dev);
};
各成员的含义如下:
(1)全局链表元素global_list:每一个pci_dev结构都通过该成员连接到全局pci设备链表pci_devices中。
(2)总线设备链表元素bus_list:每一个pci_dev结构除了链接到全局设备链表中外,还会通过这个成员连接到其所属PCI总线的设备链表中。每一条PCI总线都维护一条他自己的设备链表视图,以便描述所有连接在该PCI总线上的设备,其表头由PCI总线的pci_bus结构中的 devices成员所描述。
(3)总线指针bus:指向这个PCI设备所在的PCI总线的pci_bus结构。因此,对于桥设备而言,bus指针将指向桥设备的主总线(primary bus),也即指向桥设备所在的PCI总线。
(4)指针subordinate:指向这个PCI设备所桥接的下级总线。这个指针成员仅对桥设备才有意义,而对于一般的非桥PCI设备而言,该指针成员总是为NULL。
(5)无类型指针sysdata:指向一片特定于系统的扩展数据。
(6)指针procent:指向该PCI设备在/proc文件系统中对应的目录项。
(7)devfn:这个PCI设备的设备功能号,也成为PCI逻辑设备号(0-255)。其中bit[7:3]是物理设备号(取值范围0-31),bit[2:0]是功能号(取值范围0-7)。
(8)vendor:这是个16无符号整数,表示PCI设备的厂商ID。
(9)device:这是个16无符号整数,表示PCI设备的设备ID。
(10)subsystem_vendor:这是个16无符号整数,表示PCI设备的子系统厂商ID。
(11)subsystem_device:这是个16无符号整数,表示PCI设备的子系统设备ID。
(12)class:32位的无符号整数,表示该PCI设备的类别,其中,bit[7:0]为编程接口,bit[15:8]为子类别代码,bit [23:16]为基类别代码,bit[31:24]无意义。显然,class成员的低3字节刚好对应和PCI设置空间中的类代码。
(13)hdr_type:8位符号整数,表示PCI设置空间头部的类型。其中,bit[7]=1表示这是个多功能设备,bit[7]=0表示这是个单功能设备。Bit[6:0]则表示PCI设置空间头部的布局类型,值00h表示这是个一般PCI设备的设置空间头部,值01h表示这是个 PCI-to-PCI桥的设置空间头部,值02h表示CardBus桥的设置空间头部。
(14)rom_base_reg:8位无符号整数,表示PCI设置空间中的ROM基地址寄存器在PCI设置空间中的位置。ROM基地址寄存器在不同类型的PCI设置空间头部的位置是不相同的,对于type 0的设置空间布局,ROM基地址寄存器的起始位置是30h,而对于PCI-to-PCI桥所用的type 1设置空间布局,ROM基地址寄存器的起始位置是38h。
(15)指针driver:指向这个PCI设备所对应的驱动程式定义的pci_driver结构。每一个pci设备驱动程式都必须定义他自己的pci_driver结构来描述他自己。这个数据结构将在后面分析。
(16)无类型指针driver_data:指向驱动程式为这个PCI设备所分配的一块私有数据区。通常,设备驱动程式会为他所支持的每一种设备类型定义一个特定于设备类型的数据结构,以描述该类型设备的信息。而且,driver会为他所找到的每一个设备实例分配一个特定于该设备类型的数据结构实例。指针driver_data一般就用来指向这个有驱动程式所分配的数据结构实例。
(17)dma_mask:用于DMA的总线地址掩码,一般来说,这个成员的值是0xffffffff。数据类型dma_addr_t定义在include/asm/types.h中,在x86平台上,dma_addr_t类型就是u32类型。
(18)vendor_compatible[DEVICE_COUNT_COMPATIBLE]和device_compatible[DEVICE_COUNT_COMPATIBLE]:定义这个PCI设备和哪些设备相兼容。
(19)无符号的整数irq:表示这个PCI设备通过哪根IRQ输入线产生中断,一般为0-15之间的某个值。
(20)资源数组resource[DEVICE_COUNT_RESOURCE]:表示该设备可能用到的资源,包括:I/O断口区域、设备内存地址区域及扩展ROM地址区域。宏DEVICE_COUNT_DEVICE在pci.h头文件中被定义为常值12。不过PCI设备通常仅使用这12各资源区域中的一部分。其中,resource[5:0]分别对应于设置空间中的BAR0-BAR5(注意,桥设备只有BAR0和BAR1), resource[6]对应于设置空间中的ROM基地址寄存器所描述的ROM区域,而resource[10:7]分别对应于桥设备的地址过滤窗口。
(21)数组dma_resource[DEVICE_COUNT_DMA]:用于DMA的资源,DEVICE_COUNT_DMA为2。
(22)数组irq_resource[DEVICE_COUNT_IRQ]:用于IRQ的资源,DEVICE_COUNT_IRQ为2。
(23)name[80]数组:定义这个PCI逻辑设备的名字字符串。
(24)slot_name[8]数组:如果这个PCI逻辑设备是通过PCI插槽连接到PCI总线上的,则slot_name数组表示该插槽的名字字符串。
(25)active:被ISAPnP模块用来表示设备是否被激活。
(26)ro:被ISAPnP模块用来表示设备是否为只读设备。
(27)regs:被ISAPnP模块用来表示设备设备所支持的寄存器个数。
(28)函数指针prepare、activate和deactivate:都是仅被ISAPnP模块钩挂的函数指针。
§2.2 PCI总线描述符??pci_bus结构类型
该结构定义在include/linux/pci.h头文件中,如下所示:
struct pci_bus {
struct list_head node; /* node in list of buses */
struct pci_bus *parent; /* parent bus this bridge is on */
struct list_head children; /* list of child buses */
struct list_head devices; /* list of devices on this bus */
struct pci_dev *self; /* bridge device as seen by parent */
struct resource *resource[4]; /* address space routed to this bus */
struct pci_ops *ops; /* configuration access functions */
void *sysdata; /* hook for sys-specific extension */
struct proc_dir_entry *procdir; /* directory entry in /proc/bus/pci */
unsigned char number; /* bus number */
unsigned char primary; /* number of primary bridge */
unsigned char secondary; /* number of secondary bridge */
unsigned char subordinate; /* max number of subordinate buses */
char name[48];
unsigned short vendor;
unsigned short device;
unsigned int serial; /* serial number */
unsigned char pnpver; /* Plug & Play version */
unsigned char productver; /* product version */
unsigned char checksum; /* if zero - checksum passed */
unsigned char pad1;
};
各成员的含义如下:
(1)链表元素node:对于PCI根总线而言,其pci_bus结构通过node成员链接到本节一开始所述的根总线链表中,根总线链表的表头由一个list_head类型的全局变量pci_root_buses所描述。而对于非根pci总线,其pci_bus结构通过node成员链接到其父总线的子总线链表children中(见下面)。
(2)parent指针:指向该pci总线的父总线,即pci桥所在的那条总线。
(3)children指针:描述了这条PCI总线的子总线链表的表头。这条PCI总线的所有子总线都通过上述的node链表元素链接成一条子总线链表,而该链表的表头就由父总线的children指针所描述。
(4)devices链表头:描述了这条PCI总线的逻辑设备链表的表头。除了链接在全局PCI设备链表中之外,每一个PCI逻辑设备也通过其 pci_dev结构中的bus_list成员链入其所在PCI总线的局部设备链表中,而这个局部的总线设备链表的表头就由pci_bus结构中的 devices成员所描述。
(5)指针self:指向引出这条PCI总线的桥设备的pci_dev结构。
(6)资源指针数组resource[4]:指向应路由到这条pci总线的地址空间资源,通常是指向对应桥设备的pci_dev结构中的资源数组resource[10:7]。
(7)指针ops:指向一个pci_ops结构,表示这条pci总线所使用的设置空间访问函数。下一节将周详讨论这个数据结构。
(8)无类型指针sysdata:指向系统特定的扩展数据。
(9)指针procdir:指向该PCI总线在/proc文件系统中对应的目录项。
(10)number:这条PCI总线的总线编号(bus number),取值范围0-255。
(11)primary:表示引出这条PCI总线的“桥设备的主总线”(也即桥设备所在的PCI总线)编号,取值范围0-255。
(12)secondary:表示引出这条PCI总线的桥设备的次总线号,因此secondary成员总是等于number成员的值。取值范围0-255。
(13)subordinate:这条PCI总线的下属PCI总线(Subordinate pci bus)的总线编号最大值,他应该等于引出这条PCI总线的桥设备的subordinate值。
(13)name[48]:这条PCI总线的名字字符串。
(14)vendor和device:表示引出这条PCI总线的桥设备的厂商ID和设备ID。
(15)serial:系列号。
(16)pnpver:Plug&Play版本号。
(17)productver:产品版本号。
(18)chechsum:pci_bus结构的校验和,因改为0。
(19)pad1:为了使pci_bus结构的大小沿内存边界对齐而设立的成员,无实际意义。
下面对上述结构中的总线编号进行一下说明。假定这样一个PCI总线体系结构:根总线0上有一个PCI桥,他引出子总线bus 1,bus 1上又有一个PCI桥引出bus 2,如下图2所示:
(图2)
PCI桥的设置空间头部中定义桥两侧的主、次总线编号,及桥后面的下级总线编号的最大可能值。因此在上图中,bus 0总线的pci_bus结构中的number、primary、secondary都应该为0,因为他是通过Host/PCI桥引出的根总线;而bus 1总线的pci_bus结构中的number=secondary=1,而bus 1的primary应该为0;而bus 2总线的pci_bus结构中的number=secondary=2,其primary则应该等于1。这三条总线的subordinate值都应该等于 2。
§2.3 PCI设备链表
前面已讲过,所有的PCI设备都通过其pci_dev结构中的global_list成员链接一条“全局pci设备链表”pci_devices,表头pci_devices定义在drivers/pci/pci.c文件中:
LIST_HEAD(pci_device);
另外,同属一条PCI总线上的所有PCI设备也通过其pci_dev结构中的bus_list成员链接成一条局部这条PCI总线的“总线设备链表”,表头则由该PCI总线的pci_bus结构中的devices成员所定义。
下图3描述能非常清晰地描述能非常清晰地描述上述这两重链表的关系
(图3)
■遍历PCI设备链表的辅助宏
为了更为方便地遍历上述两类PCI设备链表(全局PCI设备链表和局部总线设备链表),Linux在头文件中pci.h定义了几个辅助宏,从而使得遍历链表的代码更为简单、易懂。他们是:
(1)宏pci_for_each_dev(dev):正向遍历全局设备链表pce_devices中的每一个PCI 设备,参数dev是个pci_dev结构类型的指针。 其定义如下:
#define pci_for_each_dev(dev) \
for(dev=pci_dev_g(pci_devices.next);dev!=pci_dev_g(&pci_devices);dev=pci_dev_g(dev->global_list.next))
上述定义中的宏pci_dev_g()用于将一个list_head类型的指针转换为一个pci_dev类型的指针(下面谈及)。对这个宏的典型使用方法如下所示:
pci_dev *dev;
……
pci_for_each_dev(dev)
{
……/* 对dev所指向的每一个pci设备进行处理 */
}
(2)宏pci_for_each_dev_reverse(dev):逆向遍历全局设备链表pci_devices中的每一个pci设备。参数dev同样也是个pci_dev结构类型的指针。其定义如下:
#define pci_for_each_dev_reverse(dev) \
for(dev=pci_dev_g(pci_devices.prev);dev!=pci_dev_g(&pci_devices);dev=pci_dev_g(dev->global_list.prev))
显然,上述宏将从全局设备链表中的最后一个元素(由pci_devices.prev所指向)开始逆向遍历整个链表。该宏的使用和pci_for_each_dev()宏相同,只是遍历方向相反而已。
(3)宏pci_dev_g(n)和pci_dev_b(n):宏pci_dev_g()用来将和全局设备链表对应的list_head类型指针 n转换为pci_dev类型的指针,宏pci_dev_b()用来将和总线局部设备链表对应的list_head类型指针n转换为pci_dev类型的指针。实际上,他们都是通过list_entry()宏来实现实际的转换工作,如下所示:
#define pci_dev_g(n) list_entry(n, struct pci_dev, global_list)
#define pci_dev_b(n) list_entry(n, struct pci_dev, bus_list)
§2.4 PCI总线链表
系统中当前存在的所有根总线都通过其pci_bus结构中的node成员链接成一条全局的根总线链表,其表头由list类型的全局变量pci_root_buses来描述。他的定义如下(drivers/pci/pci.c):
LIST_HEAD(pci_root_buses);
而根总线下面的所有下级总线则都通过其pci_bus结构中的node成员链接到其父总线的children链表中。这样,通过这两种PCI总线链表,linux内核就将所有的pci_bus结构以一种倒置树的方式组织起来。假定对于图4所示的PCI总线体系结构,他所对应的总线链表结构如图5所示。
(图4)
(图5)
类似地,宏pci_bus_b(n)则被用来将list_head类型的指针n转换为pci_bus类型的指针。该宏也是定义在include/linux/pci.h头文件中:
#define pci_bus_b(n) list_entry(n, struct pci_bus, node)