§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)

PCI总线体系结构概述相关推荐

  1. linux设备驱动之PCI总线概述

    文章目录 总线概念 PCI总线 PCI总线体系结构 PCI设备寻址 PCI寻址 配置寄存器 总线概念 总线是一种传输信号的信道:总线是连接一个或多个半导体的电气连线.总线由电气接口和编程接口组成,对于 ...

  2. PCI总线的桥与配置(一)

    在PCI体系结构中,含有两类桥片,一个是HOST主桥,另一个是PCI桥.在每一个PCI设备中(包括PCI桥)都含有一个配置空间.这个配置空间由HOST主桥管理,而PCI桥可以转发来自HOST主桥的配置 ...

  3. 浅谈PCI Express体系结构(二)

    PCI总线的信号定义 PCI总线是一条共享总线,在一条PCI总线上可以挂接多个PCI设备.这些PCI设备通过一系列信号与PCI总线相连,这些信号由地址/数据信号.控制信号.仲裁信号.中断信号等多种信号 ...

  4. PCI总线的桥与配置(二)

    PCI桥与PCI设备的配置空间 PCI设备都有独立的配置空间,HOST主桥通过配置读写总线事务访问这段空间.PCI总线规定了三种类型的PCI配置空间,分别是PCI Agent设备使用的配置空间,PCI ...

  5. 浅谈PCI Express体系结构(一)

    PCI(Peripheral Component Interconnect)总线的诞生与PC(Personal Computer)的蓬勃发展密切相关.在处理器体系结构中,PCI总线属于局部总线(Loc ...

  6. 浅谈PCI Express体系结构(三)

    PCI总线的存储器读写总线事务 总线的基本任务是实现数据传送,将一组数据从一个设备传送到另一个设备,当然总线也可以将一个设备的数据广播到多个设备.在处理器系统中,这些数据传送都要依赖一定的规则,PCI ...

  7. 浅谈PCI Express体系结构(四)

    PCI总线的中断机制 PCI总线使用INTA#.INTB#.INTC#和INTD#信号向处理器发出中断请求.这些中断请求信号为低电平有效,并与处理器的中断控制器连接.在PCI体系结构中,这些中断信号属 ...

  8. PCI总线的发展过程

    PCI总线是由ISA(Industy Standard Architecture)总线发展而来的.PCI总线是计算机的1/0总线,在90年代时替代了ISA总线,成为计算机中的局部总线一直使用至今.PC ...

  9. pci总线定时协议_汽车总线测试的“解忧杂货店”

    "我的回答之所以发挥了作用,原因不是别的,是因为大家自己很努力." -- 东野圭吾<解忧杂货店> 相信很多读者都看过东野圭吾的书<解忧杂货店>或者同名电影, ...

最新文章

  1. c语言编程 构建围墙,c语言程序设计朝盛 综合程序练习题.ppt
  2. Verilog代码规范I
  3. MySQL慢查询日志总结
  4. 刀片服务器 如何增加硬盘,IBM为刀片服务器添加新SAS及固态硬盘
  5. 开源 一套 Blazor Server 端精致套件
  6. 前端学习(1529):钩子函数--文档分析
  7. 中国首台千万亿次超级计算机,中国首台千万亿次超级计算机首批设备开始试用...
  8. Leetcode--448. 找到所有数组中消失的数字
  9. 1012 The Best Rank (25)
  10. 服务器硬件oid,HPE ProLiant DL580 Gen10 服务器
  11. 欢乐的跳(洛谷P1152题目链接,Java语言描述)
  12. mysql 联表删除limit_sql连表删除 | 深蓝的blog
  13. win10 + VS2015 + EF6 + MySQL
  14. WDS+MDT部署系统
  15. linux 刷新网络配置,3 Linux 网络配置
  16. pc端和移动端有什么区别?
  17. js原生创建元素createElement,动态插入js
  18. mac触控板 鼠标中键_如何使用触控板,鼠标或键盘在任何Mac上单击鼠标右键
  19. 用Python在图片上添加文字
  20. java中API什么意思

热门文章

  1. html复选框值改变后事件,javascript – 从onclick/onchange事件获取HTML值的复选框
  2. Java代理模式——静态代理动态代理
  3. 第13章 程序的动态加载和执行(一,引导)
  4. owncloud nginx php,nginx配置owncloud记录。
  5. java windows so文件_windows下编译使用NDK,调用SO文件 | 学步园
  6. python导入模块不在同一文件夹下_Python小技之自定义不同文件夹下模块导入问题...
  7. Beta冲刺(9/7)——2019.5.31
  8. bwa比对软件的使用以及其结果文件(sam)格式说明
  9. php 安装rabtmq amqp 扩展
  10. HDU1023 Train Problem II