[虚拟化/云][全栈demo] 为qemu增加一个PCI的watchdog外设(五)
目的:
1. 了解PCI的基本知识,为完成watchdog的设备做准备。
准备知识:
简单的说,PCI 设备分3个空间。 配置空间,IO空间,内存地址空间。
PCI设备厂家决定了外设是使用IO空间还是IO内存空间。 我们通过读取配置空间的bar寄存器的最低位bit0来决定是该设备使用的是IO空间还是内存地址空间。
计算机一启动,bois或者linux会根据域,总线号、设备号和功能号,按照一定的算法,扫描PCI设备,读取设备配置空间的信息。最主要的包括厂商ID,设备ID和bar寄存器中设备的外设地址。
以下的这些内容可以不看。
具体的PCI的知识可参考: http://tldp.org/HOWTO/PCI-HOWTO.html
PCI硬件结构(PCI系统示意图):
了解的概念
domain, bus,devcie,function(PCI设备 = "域号, 总线号, 设备号, 和功能号"的组合)
image: http://blog.sina.com.cn/s/blog_6472c4cc0100qg5i.html
image: http://blog.sina.com.cn/s/blog_6472c4cc0100qtdo.html
详情: http://blog.sina.com.cn/s/blog_6472c4cc0100qbvs.html
PCI 寻址
每个 PCI 外设有一个总线号, 一个设备号, 一个功能号标识. PCI 规范允许单个系统占用多达 256 个总线, 但是因为 256 个总线对许多大系统是不够的, Linux 现在支持 PCI 域. 每个 PCI 域可以占用多达 256 个总线. 每个总线占用 32 个设备, 每个设备可以是一个多功能卡(例如一个声音设备, 带有一个附加的 CD-ROM 驱动)有最多 8 个功能. 因此, 每个功能可在硬件层次被一个 16-位地址或者 key , 标识. Linux 的设备驱动编写者, 然而, 不需要处理这些二进制地址, 因为它们使用一个特定的数据结构, 称为 pci_dev, 来在设备上操作.
$ lspci | cut -d: -f1-3
0000:00:00.0 Host bridge
0000:00:00.1 RAM memory
....
0000:00:14.0 VGA compatible controller
$ cat /proc/bus/pci/devices | cut -f1
0000
0001
...
00a0
0x00a0(0b10100000) 意思是 0000:00:14.0 当划分为域(16位), 总线(8位), 设备(5位)和功能(3位).
驱动概念
与USB驱动总线型的驱动类似, Linux PCI设备驱动实际包括Linux PCI设备驱动和设备本身驱动两部分。
就是Linux PCI驱动是内核自带的,或者说内核帮你写好了!而我们需要完成的就是设备本身的驱动,比如网卡驱动等。
这个与windows的驱动也是类似的。比如说我们需要开发一个基于USB的windows的打印机,我们只需要关心打印机的驱动即可。
三种地址空间
PCI I/O空间、PCI内存地址空间和PCI配置空间。
PCI I/O空间和PCI内存地址空间由设备驱动程序(即上面提到的设备本身驱动)使用。
PCI配置空间由Linux PCI初始化代码使用,这些代码用于配置PCI设备,比如中断号以及I/O或内存基地址。
枚举过程
熟悉USB开发的工程师知道USB host跟device的第一次通信都是采用0x00相同的地址,然后进行配置,分配给一个新的唯一地址。
PCI的枚举过程类似。 http://blog.csdn.net/linuxdrivers/article/details/5849698
对于i386结构的处理器,PCI总线的设计者在I/O地址空间保留了8个字节用于枚举过程中的配置,那就是0xCF8~0xCFF。这8个字节构成了两个32位 的寄存器,第一个是“地址寄存器”0xCF8,第二个是“数据寄存器”0xCFC。要访问某个设备中的某个配置寄存器时,CPU先往地址寄存器中写入目标 地址,然后通过数据寄存器读写数据。不过,写入地址寄存器的目标地址是一种总线号、设备号、功能号以及设备寄存器地址在内的综合地址。
这里的总线号、设备号和功能号是对配置寄存器地址的扩充,就是上面提到的附加的其他条件。首先每个PCI总线都有个总线号,主总线的总线号为0,其余的则 由CPU在枚举阶段每当探测到一个PCI桥时便为其指定一个,依次递增。设备号一般代表着一块PCI接口卡(更确切的说是PCI总线接口芯片),通常取决 于插槽的位置。每块PCI接口卡上可以有若干个功能模块,这些功能模块共用一个PCI总线接口芯片,包括其中用于地址映射的电子线路,以降低成本。从逻辑 的角度说,每个“功能”实际上就是一个设备(看过USB设备驱动的人很眼熟吧 呵呵),所以设备号和功能号合在一起又可以称作“逻辑设备号”,而每块卡上最多可以容纳8个设备。显然,这些字段(指整个32bit)结合在一起就惟一确 定了系统中的一项PCI逻辑设备。开始时,只有0号总线可以访问,在扫描0号总线时如果发现上面某个设备是PCI桥,就为之指定一个新的总线号,例如1, 这样1号总线就可以访问了,这就是枚举阶段的任务之一。
IO/MMIO
IO作为CPU和外设交流的一个渠道,主要分为两种,一种是Port I/O,一种是MMIO(Memory mapping I/O)。
I/O地址空间
我们常说的I/O端口,它实际上的应该被称为I/O地址空间。
对于x86架构来说,通过IN/OUT指令访问。PC架构一共有65536个8bit的I/O端口,组成64KI/O地址空间,编号从0~0xFFFF。 连续两个8bit的端口可以组成一个16bit的端口,连续4个组成一个32bit的端口。I/O地址空间和CPU的物理地址空间是两个不同的概念,例如 I/O地址空间为64K,一个32bit的CPU物理地址空间是4G。
MMIO
MMIO我们可以想访问内存一样访问IO端口。
MMIO占用CPU的物理地址空间,对它的访问可以使用CPU访问内存的指令进行。一个形象的比喻是把文件用mmap()后,可以像访问内存一样访问文
件、同样,MMIO是用访问内存一样的方式访问I/O资源,如设备上的内存。MMIO不能被cache,(有特殊情
况,如VGA)。
Port I/O和MMIO的主要区别在于
1)前者不占用CPU的物理地址空间,后者占有(这是对x86架构说的,一些架构,如IA64,port I/O占用物理地址空间)。
2)前者是顺序访问。也就是说在一条I/O指令完成前,下一条指令不会执行。例如通过Port I/O对设备发起了操作,造成了设备寄存器状态变化,这个变化在下一条指令执行前生效。uncache的MMIO通过uncahce memory的特性保证顺序性。
3)使用方式不同
由于port
I/O有独立的64KI/O地址空间,但CPU的地址线只有一套,所以必须区分地址属于物理地址空间还是I/O地址空间。早期的CPU有一个M/I针脚来
表示当前地址的类型,后来似乎改了。刚才查了一下,叫request command line,不知道是不是一个针脚。
IBM
PC架构规定了一些固定的I/O端口,ISA设备通常也有固定的I/O端口,这些可以通过ICH(南桥)的规范查到。PCI设备的I/O端口和MMIO基
地址通过设备的PCI configure space报告给操作系统,这些内容以前的帖子都很多,可以查阅一下。
通常遇到写死在I/O指令中的I/O端口,如果不是ISA设备,一般都是架构规定死的端口号,可查阅规范。
BAR (http://blog.sina.com.cn/s/blog_6472c4cc0100qnht.html)
PCI bar(PCI base address register)。PCI bar就是该设备可用port i/o和mmio访问的寄存器的基地址。除了寄存器,还有一种称为扩展rom,也是通过mmio访问的。而ioremap的作用就是把pci bar(pci bar是物理地址)映射到虚拟地址空间中。所以它和pci_read_config_dword是下列关系:
pci_read_config_dword得到PCI bar ----> ioremap映射PCI bar到虚拟地址空间, 返回一个虚拟地址,以后通过该地址读写设备的寄存器。
可以通过读取配置空间的bar寄存器{bar0: 0x10, bar1: 0x14, bar2: 0x18, bar3: 0x1C, bar4: 0x20, bar5: 0x24} 来获取基地址。 如果读到的地址为是奇数,那么这个地址是个IO端口地址。 如果读到的是偶数,那么这个地址是IO内存地址。
见这个帖子 http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=1061711&page=1
iommu相关的PCI内容
http://download.intel.com/technology/computing/vptech/Intel%28r%29_VT_for_Direct_IO.pdf
这个对于我们完成watchdog的驱动没有关系,可以略过。这个对于深入虚拟化需要了解。
在没有IOMMU的情况下,设备(指32bit或64bit设备,老的16bit的不提)的DMA操作可以访问整个物理地址空间,所以理论上设备可以向操 作系统的代码段、数据段等内存区域做DMA,从而破坏整个系统。当然,通常来说不会有这样的设备。IOMMU的出现,可以实现地址空间上的隔离,使设备只 能访问规定的内存区域。
简要说一下intel的IOMMU怎么做到这点的:
目前PC架构最多有256PCI总线,于是IOMMU用一个称为root entry的数据结构描述PCI总线,总共256个root
entry构成一张表。每条PCI总线最多允许256个设备,IOMMU用context
entry描述一个PCI设备(或者是PCI桥),256个context entry构成一张表。所以就有了如图的关系。我们知道,PCI设备用
{BUS:DEV:FUNC}
(当然,还有个segment,不过似乎PC架构都只有一个segment,这个暂时忽略)描述一个设备。所以对于一个特定设备,用bus号做索引
root entry表,用dev号索引context entry表可以找到描述该设备的的context entry。context
entry中有一个指针指向一章I/O页表,当设备发起DMA操作时,IOMMU会根据该页表把设备的DMA地址转换成该设备可以访问内存区域的地址。
所以只要为设备建一张I/O页表,就可以使设备只能访问规定的内存区域了。当然,也可以把该页表当成跳板,让只能寻址32bit地址空间的设备访问到64bit地址空间中去。
IOMMU是对于设备发起DMA操作来说的,你可以理解成设备用于做DMA的地址是一个虚拟地址(这个虚拟地址和我们平时说的那个不一样,是指设备DMA 用的地址不是真实的物理地址,没有IOMMU的情况下用的是物理地址)。也有称这个虚拟地址为总线地址的,但我认为不恰当,容易让人误解。
对于eeprom的访问,它又和mmio还有iommu都没有关系。eeprom是个串行设备,它不是被ioremap到虚拟地址空间访问,而是通过
PCI bar报告一个eeprom寄存器给操作系统,这个寄存器有一个bit的di(data in)、一个bit的do(data
out)、一个bit的clk(时钟)。时钟在下降沿生效,写的时候通过di一个bit一个bit的写,读的时候通过do一个bit一个bit的读。
SRIOV相关的知识
http://docs.oracle.com/cd/E38902_01/html/E38873/glbzi.html
REF: