PCIPCIe硬件相关知识
PCI/PCIe硬件相关知识
Linux下PCI驱动实现(硬件篇)
首先要说明一点,由于作者对内核也不是完全熟悉,所以做内核编程时喜欢猜测,即根据对内核的了解去猜测内核会怎么做,文章有部分内核行为是作者自己猜测,如有错误,欢迎批评指正。
1.PCI历史
PCI协议经历
ISA (Industry Standard Architecture)
MCA (Micro Channel Architecture)
EISA (Extended Industry Standard Architecture)
VLB (VESA Local Bus)
PCI (Peripheral Component Interconnect)
PCI-X (Peripheral Component Interconnect eXtended)
AGP (Accelerated Graphics Port)
PCI Express (Peripheral Component Interconnect Express)
发展史,技术发展是为了解决当前问题,这里的问题就是传输速度,盗用知乎上老狼的一张图
可以看到主频的增加促进了传输速度的增加,主频的增加会带来另一个问题,那就是相互间的干扰,PCI设备是并行通信,频率增加,各条线路不可避免就会互相干扰,于是PCIe诞生了,PCIe是串行通信。关于PCIe在以后的章节介绍,本篇文章只考虑PCI协议。
2.PCI总线
一颗典型的PCI总线树如图(还是盗用老狼的图):
熟悉总线模型的同学应该都了解这种拓朴结构,及一个根总线,总线挂接设备或者子总线,当然子总线需要桥(bridge)连接。这种结构限制我们在总线上找一个设备时,需要指定总线号及设备号。
3.PCI协议简介
PCI有三种地址空间:I/O空间、内存空间、配置空间。其中I/O空间和内存空间由设备驱动使用,也就是驱动可以对这两类空间进行数据读写。而配置空间由Linux PCI初始化代码使用。
系统启动之后调用PCI初始化,读取配置空间,然后初始化PCI设备,具体的配置空间如下,
配置空间共有64byte大小,Vender ID是厂家ID,这个编号需要向PCI SIG组织申请当然如果自己做也可以,只是不被国际认可,Device ID是厂家自定义的设备号,这两个寄存器的值会在设备枚举过程中被内核抓取,并以此来甄选驱动;Status,设备状态字,百度之后并没有资料详细介绍每一个bit的作用,查找Linux源码,找到如下字段:
#define PCI_STATUS 0x06 /* 16 bits */
#define PCI_STATUS_INTERRUPT 0x08 /* Interrupt status */
#define PCI_STATUS_CAP_LIST 0x10 /* Support Capability List */
#define PCI_STATUS_66MHZ 0x20 /* Support 66 MHz PCI 2.1 bus */
#define PCI_STATUS_UDF 0x40 /* Support User Definable Features [obsolete] */
#define PCI_STATUS_FAST_BACK 0x80 /* Accept fast-back to back */
#define PCI_STATUS_PARITY 0x100 /* Detected parity error */
#define PCI_STATUS_DEVSEL_MASK 0x600 /* DEVSEL timing */
#define PCI_STATUS_DEVSEL_FAST 0x000
#define PCI_STATUS_DEVSEL_MEDIUM 0x200
#define PCI_STATUS_DEVSEL_SLOW 0x400
#define PCI_STATUS_SIG_TARGET_ABORT 0x800 /* Set on target abort */
#define PCI_STATUS_REC_TARGET_ABORT 0x1000 /* Master ack of " */
#define PCI_STATUS_REC_MASTER_ABORT 0x2000 /* Set on master abort */
#define PCI_STATUS_SIG_SYSTEM_ERROR 0x4000 /* Set when we drive SERR */
#define PCI_STATUS_DETECTED_PARITY 0x8000 /* Set on parity error */
源码里的解释一目了然,这里就不多说了。HeaderType表示设备类型是PCI设备还是PCI桥。下面详细说明一下Base Address Registers简称BAR。
Base Address Registers:有三种功能,1.决定PCI设备映射方式,分别是IO和Memory映射。2.决定映射空间大小。3.存储映射基地址。
内存映射如图:
bit0为只读,且值为0,
bit2-bit1:00表示映射成32位地址,10表示映射成64位地址。
bit3:表示是否支持预读,1:支持 0:不支持
I/O映射如下图,
Bit0:为只读,且值为1,
Bit1:Reserved
当系统上电初始化之后,步骤如下
给BAR全部写1,这样具有可写属性的位被置1。
读出BAR的值,并clear 上图中特殊编码的值,(IO 中bit0,bit1, memory中bit0-3)。
将读出来的值加1,得到的值就是内存空间大小
网上搜到的说法是,如果需要1MB的空间,就设置为高12位可读写,bit20-4为只读且为0。
按照我自己的理解,高12位 + 只读位(20-4+1) + 特殊位(4位) = 33,也就是网上说法是错误的,不可能有33位2进制位,正确的设置应该是:
高12位 + 只读位(19-4+1) + 特殊位(4位),按照规则进行位运算正好是1MB。
BAR的第三个功能,存储基地址,当内核读取到所有PCI的BAR需要的地址大小之后,动态分配映射地址,并将地址写入BAR寄存器,读取BAR寄存器的值即是数据地址。
那么如何读取BAR寄存器的值呢,在i386中只需要往寄存器0xCF8中写地址,再去0xCFC中读数据即可。如何写呢,盗用别人的一张图如下:
bit31置1,bit30-bit24:保留,bit23-bit16:即第2节说的总线号,bit15-bit11:总线分配给具体设备的设备号,这样就找到的具体的物理设备,bit10-bit8:逻辑号,也就是说一个物理设备可以有多个逻辑设备,及一个物理设备有多个功能(一般都将此设置为0),bit7-bit2:寄存器号,这样就找到了我们需要读取的BAR。
那总线号和设备号怎么得到呢,这个在linux系统里会保存到代表具体设备的结构体变量里。
到这里作者还有个疑问,因为做过数字电路设计的人都知道,所谓通信无非就是数据线,地址线,控制线三类,然后先控制信号,然后地址信号,再数据信号,这是现代总线通信的基本模型。在PCI通信里这个模型怎么实现的呢。先看一张引脚连接图:
主要看左边,右边是64位扩展,有兴趣的同学可以了解下,有32位数据地址总线AD[31:00],4位控制总线C/BE[3::0],还有一些Interface Control控制线。
来看看写时序图:
首先了解一下命令控制脚C/BE# :