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
  当系统上电初始化之后,步骤如下
1. 给BAR全部写1,这样具有可写属性的位被置1。
2. 读出BAR的值,并clear 上图中特殊编码的值,(IO 中bit0,bit1, memory中bit0-3)。
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# :
这里写图片描述
在FRAME#有效后的第一个时钟周期内,AD上传输的是要写入目标PCI设备的地址信息,C/BE#上传输的是命令类型(I/O写命令为0011),DEVSEL#信号有效后,表明目标PCI设备已经被选择到,IRDY#和TRDY#同时有效后,主PCI设备向目标PCI设备中传输要写入的数据,在第5个时钟周期时,IRDY#和TRDY#同时变为无效状态,AD总线上被插入一个等待周期,第6和第7个时钟周期时,IRDY#有效,但是TRDY#无效,传输仍然不能有效进行,总线上被继续插入两个等待周期,第8个时钟周期时,IRDY#和TRDY#都有效,数据传输继续。
中断线寄存器(Interrupt Line:0x3B):
对于X86 系列的PC 机,各个插槽的INTA# - INTD#引脚由主板设计者通过可编程路由器接到由主从两个8259A 组成的系统中断控制器的IRQ0 - IRQ15 引脚共计16个引脚中的未使用引脚上。中断线寄存器(Interrupt Line)用于保存中断路由信息的寄存器,在初始化和配置系统时,HOST把路由信息写入到该寄存器。在PCI 接口卡配置空间中,该寄存器的值表明设备的中断引脚( INTA# - INTD#)被连接到系统中断控制器的哪一个引脚(1RQO - IRQ15中的哪一个)上了。设备本身不使用这个值,设备驱动和操作系统使用该值来决定中断的优先权和中断矢量信息, 义,值0 —15 对应16个IRQ 引脚号,值255 用于表示“未知”或“没有连接到中断控制器”,值16 到254 保留。例如:某设备的INTA#被路由至IRQ3 脚,其中断线寄存器的值会设置为3。
中断引脚寄存器(Interrupt Pin:0x3C):
它是一个8 位的寄存器,由接口设计者根据PCI设备使用的PCI 总线中断引脚(INTA# - INTD#)来设置:如果设备使用INTA#脚来申请中断,该寄存器应写入1;如果设备使用INTB#脚来申请中断,该寄存器应写入2;如果设备使用INTC#脚来申请中断,该寄存器应写入3 ;如果设备使用INTD#脚来申请中断,该寄存器应写入4;如果设备不使用中断,该寄存器应写入0,0x05 到0xFF为保留值。
  有兴趣深入了解PCI通信协议的同学,可以看PCI规范中文版。
  接下来几篇博客,会以Xilinx公司的XDMA设备驱动为例,详细讲解windows和Linux下PCIe驱动编写。
  

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值