一条PCI总线一般有32个接口,即可以连接32个PCI接口卡,而一个接口卡对应一个外部设备,注意这里的外部设备可以有多个功能(最多八个),每一个功能称为逻辑设备。每一个逻辑设备对应一个PCI配置空间。对于逻辑设备后面还会详细解释,这里先说配置空间的问题。PCI配置空间可以说是记录了关于此设备的详细信息。PCI配置空间最大256个字节,其中起先的64个字节的格式是预定义好的。当然并非所有的项都必须填充,位置是固定了,没有用到可以填充0。而前16个字节的格式是一定的。包含头部的类型、设备的总类、设备的性质以及制造商等。
PCi配置空间前64个字节格式如下:
需要注意的有一下几项:
ClassCode 用于将设备分到具体的功能组,该字段分为两部分,前8个bit表示基类即大类别,后8个比特表示基类的一个子类。比如PCI_BASE_CLASS_STORAGE表示大类大容量存储器,而PCI_CLASS_STORAGE_IDE表明这个IDE控制器。
HeaderType表明头部类型。一般区分为0型头部(PCI设备)1型头部(PCI桥),注意不同头部的配置空间格式有差异。这里我们主要先描述0型头部即普通PCi设备的配置空间。
PCI HeaderType为一个字节的大小,最高位为0表示单功能,最高位为1表示多功能(即前面描述的逻辑设备),单功能情况下一个PCI设备就是一个逻辑设备。低7位表示头部类型。
前16个字节都是一些基本的信息就不在多说,重点看下接下来的6个BAR空间。每个BAR记录了该设备映射的一段地址空间。为了区分IO空间和IO内存,这里我们分开描述:
当BAR最后一位为0表示这是映射的IO内存,为1是表示这是IO 端口,当是IO内存的时候1-2位表示内存的类型,bit 2为1表示采用64位地址,为0表示采用32位地址。bit1为1表示区间大小超过1M,为0表示不超过1M.bit3表示是否支持可预取。
而相对于IO内存,当最后一位为1时表示映射的IO地址空间。IO地址空间一般不支持预取,所以这里是29位的地址。
一般情况下,6个BAR是足够使用的,大部分情况都是3-4个BAR。而这些BAR映射的空间是连续的,即这些BAR共同描述设备的地址空间范围
除了6个基本的BAR空间们还有一个额外的配置ROM区间,区间最低位表示是否使用ROM区间,高21位表示地址。中间的是保留项。其余原理和上面类似。
接下来需要注意的就是中断,因为大部分外设和系统交互就是通过中断的方式。。
由配置空间中的IRQ Pin决定设备是否支持中断,1表示支持,0表示不支持。假如支持中断,IRQ Line表记录下中断号。
PCI桥的配置空间
PCI桥同样是连接在PCI总线接口卡上的一个设备,只不过是一个桥设备,连接一条PCI总线。既然同属于设备,那么它同样也就有设备的配置空间,只是它的配置空间和普通设备的有些差异。PCI桥的配置空间在系统软件遍历PCI总线树的时候配置,并不需要专门的PCI驱动,故称为透明桥。PCI桥连接两条总线,和HOst 桥近的称为上游总线(Primary Bus ),远的一条称为下游总线(Secondry Bus)。PCI桥的配置空间在前16个字节的格式和普通PCI设备并无区别,另外,桥还保留了普通设备的前两个BAR空间。所以从配置空间的0x18开始有了桥设备自身的配置格式。如前所述,桥设备记录了上游总线和下游总线,以及桥下最大的总线号。这里还有一个比较重要的概念就是窗口。在PCI桥的配置空间有三个窗口:IO地址区间窗口、存储器区间窗口、可预取存储器地址窗口。实际上每个窗口都是一段地址区间,就像一个门,规定了桥下设备映射的区间。从北桥出来的地址,如果在该区间内,就可以穿过该桥到达次级总线,这样依次寻找设备。反过来,从南桥出来的地址及由设备发出的,只有地址不在该区间范围内才可以穿过该桥。因为同一条总线上的设备交互不需要外部空间。就像是内网传输和公网传输一样的道理。
内核中关于桥配置空间的定义如下:
/* Header type 1 (PCI-to-PCI bridges) */
#define PCI_PRIMARY_BUS 0x18 /* Primary bus number */
#define PCI_SECONDARY_BUS 0x19 /* Secondary bus number */
#define PCI_SUBORDINATE_BUS 0x1a /* Highest bus number behind the bridge */
#define PCI_SEC_LATENCY_TIMER 0x1b /* Latency timer for secondary interface */
#define PCI_IO_BASE 0x1c /* I/O range behind the bridge */
#define PCI_IO_LIMIT 0x1d
#define PCI_IO_RANGE_TYPE_MASK 0x0fUL /* I/O bridging type */
#define PCI_IO_RANGE_TYPE_16 0x00
#define PCI_IO_RANGE_TYPE_32 0x01
#define PCI_IO_RANGE_MASK (~0x0fUL) /* Standard 4K I/O windows */
#define PCI_IO_1K_RANGE_MASK (~0x03UL) /* Intel 1K I/O windows */
#define PCI_SEC_STATUS 0x1e /* Secondary status register, only bit 14 used */
#define PCI_MEMORY_BASE 0x20 /* Memory range behind */
#define PCI_MEMORY_LIMIT 0x22
#define PCI_MEMORY_RANGE_TYPE_MASK 0x0fUL
#define PCI_MEMORY_RANGE_MASK (~0x0fUL)
#define PCI_PREF_MEMORY_BASE 0x24 /* Prefetchable memory range behind */
#define PCI_PREF_MEMORY_LIMIT 0x26
#define PCI_PREF_RANGE_TYPE_MASK 0x0fUL
#define PCI_PREF_RANGE_TYPE_32 0x00
#define PCI_PREF_RANGE_TYPE_64 0x01
#define PCI_PREF_RANGE_MASK (~0x0fUL)
#define PCI_PREF_BASE_UPPER32 0x28 /* Upper half of prefetchable memory range */
#define PCI_PREF_LIMIT_UPPER32 0x2c
#define PCI_IO_BASE_UPPER16 0x30 /* Upper half of I/O addresses */
#define PCI_IO_LIMIT_UPPER16 0x32
/* 0x34 same as for htype 0 */
/* 0x35-0x3b is reserved */
#define PCI_ROM_ADDRESS1 0x38 /* Same as PCI_ROM_ADDRESS, but for htype 1 */
/* 0x3c-0x3d are same as for htype 0 */
#define PCI_BRIDGE_CONTROL 0x3e
#define PCI_BRIDGE_CTL_PARITY 0x01 /* Enable parity detection on secondary interface */
#define PCI_BRIDGE_CTL_SERR 0x02 /* The same for SERR forwarding */
#define PCI_BRIDGE_CTL_ISA 0x04 /* Enable ISA mode */
#define PCI_BRIDGE_CTL_VGA 0x08 /* Forward VGA addresses */
#define PCI_BRIDGE_CTL_MASTER_ABORT 0x20 /* Report master aborts */
#define PCI_BRIDGE_CTL_BUS_RESET 0x40 /* Secondary bus reset */#define PCI_BRIDGE_CTL_FAST_BACK 0x80 /* Fast Back2Back enabled on secondary interface */
下面说说PCI设备的地址空间:
前面也简单介绍了下PCI设备的地址空间支持PIO和MMIO,即IO端口和IO内存。下面详细分析下这两种方式:
PIO
IO端口的编址是独立于系统的地址空间,其实就是一段地址区域,所有外设的地址都映射到这段区域中。就像是一个进程内部的各个变量,公用进程地址空间一样。不同外设的IO端口不同。访问IO端口需要特殊的IO指令,OUT/IN,OUT用于write操作,in用于read操作。在此基础上,操作系统实现了读写不同大小端口的函数。为什么说是不同大小呢??因为前面也说到,IO端口实际上是一段连续的区域,每个端口理论上是字节为单位即8bit,那么要想读写16位的端口只能把相邻的端口进行合并,32位的端口也是如此。
例如下面的汇编指令: