首页 > 代码库 > PCI 设备详解一

PCI 设备详解一

2016-10-09


其实之前是简单学习过PCI设备的相关知识,但是总感觉 自己的理解很函数,很多东西说不清楚,正好今天接着写这篇文章自己重新梳理一下,文章想要分为三部分,首先介绍PCI设备硬件相关的知识,然后介绍LINux内核中对PCI设备的支持。本节讲第一部分。

PCI总线在目前计算机总线系统中占据举足轻重的地位,其良好的扩展性,地址统一分配和总线竞争的处理相对于其他总线而言都具有绝对优势。

扩展性:
先说其扩展性,PCI总线上存在若干PCI设备插槽,当PCI插槽无法满足需求,就可以通过PCI桥扩展PCI设备,一个PCI桥把一个PCI总线连在一个PCI插槽上,作为PCI的一个设备。例如CPU通过“宿主-PCI桥与一条PCI总线相连,此总线成为“主PCI总线”,当通过PCI桥扩展PCI总线时,扩展的总线成为“从总线”,当然还可以通过其他的桥比如“PCI-ISA”桥扩展ISA总线,所以这样通过PCI-PCI桥可以构筑起一个层次的、树状的PCI系统结构,对于上层的总线而言,连接在这条总线上的PCI桥也是一个设备,但是这是一种特殊的设备。
其PCI树状结构如图所示:
技术分享

一条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设备的地址空间支持PIO和MMIO,即IO端口和IO内存。下面详细分析下这两种方式:

PIO

 IO端口的编址是独立于系统的地址空间,其实就是一段地址区域,所有外设的地址都映射到这段区域中。就像是一个进程内部的各个变量,公用进程地址空间一样。不同外设的IO端口不同。访问IO端口需要特殊的IO指令,OUT/IN,OUT用于write操作,in用于read操作。在此基础上,操作系统实现了读写不同大小端口的函数。为什么说是不同大小呢??因为前面也说到,IO端口实际上是一段连续的区域,每个端口理论上是字节为单位即8bit,那么要想读写16位的端口只能把相邻的端口进行合并,32位的端口也是如此。

例如下面的汇编指令:
OUT 21h,al
0x21是8259A中断控制器的中断屏蔽寄存器,该指令将Intel X86处理器的al寄存器中的值写到中断寄存器的控制寄存器中,从而达到屏蔽某些中断信号的目的。类似的,在下面的汇编指令中:
IN al,20h
0x20是8259A中断控制器的正在服务寄存器,该指令将此寄存器中的状态值传递到处理器的al寄存器中。
无论是windows还是Linux都会上述指令做了封装以满足读写不同长度端口的需要。不过这种方式缺点也比较明显,一般情况CPU分配给IO端口的空间都比较小,在当前外设存储日益增大的情况下很难满足需要。另一方面,

 

MMIO

IO内存是直接把寄存器的地址空间直接映射到系统地址空间,系统地址空间往往会保留一段内存区用于这种MMIO的映射(当然肯定是位于系统内存区),这样系统可以直接使用普通的访存指令直接访问设备的寄存器,随着计算机内存容量的日益增大,这种方式更是显出独特的优势,在性能至上的理念下,使用MMIO可以最大限度满足日益增长的系统和外设存储的需要。所以当前其实大多数外设都是采用MMIO的方式。

 

还有一种方式是把IO端口空间映射到内存空间,这样依然可以通过正常的访存指令访问IO端口,但是这种方式下依然受到IO空间大小的制约,可以说并没有解决实际问题。

 

但是上述方案只适用于在外设和内存进行小数据量的传输时,假如进行大数据量的传输,那么IO端口这种以字节为单位的传输就不用说了,IO内存虽然进行了内存映射,但是其映射的范围大小相对于大量的数据,仍然不值一提,所以即使采用IO内存仍然是满足不了需要,会让CPU大部分时间处理繁琐的映射,极大的浪费了CPU资源。那么这种情况就引入了DMA,直接内存访问。这种方式的传输由DMA控制器控制,CPU给DMA控制器下达传输指令后就转而处理其他的事务,然后DMA控制器就开始进行数据的传输,在完成数据的传输后通过中断的方式通知CPU,这样就可以极大的解放CPU。当然本次讨论的重点不在DMA,所以对于DMA的讨论仅限于此。

下篇文章会结合LInux源代码分析下Linux内核中对PCI设备的支持

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

技术分享
 

PCI 设备详解一