PCIe 基础(一)操作配置空间

PCI配置空间

PCI有三种地址空间:I/O空间,内存地址空间,PCI配置空间。在启动时bootloader
或者内核会遍历PCI总线并分配资源,如中断和内存,设备驱动程序通过PCI配置空间
找到资源分配。大小为256字节。
配置空间图:

PCIe 基础(一)操作配置空间_第1张图片
(1) Device ID和Vendor ID寄存器
这两个寄存器的值由PCISIG分配,只读。其中Vendor ID代表PCI设备的生产厂商,
而Device ID代表这个厂商所生产的具体设备。如Intel公司的基于82571EB芯片的
系列网卡,其Vendor ID为0x8086[1],而Device ID为0x105E[2]。
(2) Revision ID和Class Code寄存器
这两个寄存器只读。其中Revision ID寄存器记载PCI设备的版本号。该寄存器可以
被认为是Device ID寄存器的扩展。
(3) Header Type寄存器
该寄存器只读,由8位组成。第7位为1表示当前PCI设备是多功能设备,为0表示为
单功能设备。第6~0位表示当前配置空间的类型,为0表示该设备使用PCI Agent设
备的配置空间,普通PCI设备都使用这种配置头;为1表示使用PCI桥的配置空间,
PCI桥使用这种配置头;为2表示使用Cardbus桥片的配置空间,Card Bus桥片使用
这种配置头,本篇对这类配置头不感兴趣。系统软件需要使用该寄存器区分不同类
型的PCI配置空间,该寄存器的初始化必须与PCI设备的实际情况对应,而且必须为
一个合法值。
(4) Subsystem ID和Subsystem Vendor ID寄存器
这两个寄存器和Device ID和Vendor ID类似,也是记录PCI设备的生产厂商和设备
名称。但是这两个寄存器和Device ID与Vendor ID寄存器略有不同。下文以一个
实例说明Subsystem ID和Subsystem Vendor ID的用途。Xilinx公司在FGPA中
集成了一个PCIe总线接口的IP核,即LogiCORE。用户可以使用LogiCORE设计各
种各样基于PCIe总线的设备,但是这些设备的Device ID都是0x10EE,而Vendor
ID为0x0007[3]。
(6) Expansion ROM base address寄存器
有些PCI设备在处理器还没有运行操作系统之前,就需要完成基本的初始化设置,
比如显卡、键盘和硬盘等设备。为了实现这个“预先执行”功能,PCI设备需要
提供一段ROM程序,而处理器在初始化过程中将运行这段ROM程序,初始化这
些PCI设备。Expansion ROM base address记载这段ROM程序的基地址。
(7) Capabilities Pointer寄存器
在PCI设备中,该寄存器是可选的,但是在PCI-X和PCIe设备中必须支持这个寄
存器,Capabilities Pointer寄存器存放Capabilities寄存器组的基地址,PCI设
备使用Capabilities寄存器组存放一些与PCI设备相关的扩展配置信息。
(8) Interrupt Line寄存器
这个寄存器是系统软件对PCI设备进行配置时写入的,该寄存器记录当前PCI设备
使用的中断向量号,设备驱动程序可以通过这个寄存器,判断当前PCI设备使用处
理器系统中的哪个中断向量号,并将驱动程序的中断服务例程注册到操作系统中
[4]。该寄存器由系统软件初始化,其保存的值与8259A中断控制器相关,该寄存
器的值也是由PCI设备与8259A中断控制器的连接关系决定的。如果在一个处理器
系统中,没有使用8259A中断控制器管理PCI设备的中断,则该寄存器中的数据并
没有意义。在多数PowerPC处理器系统中,并不使用8259A中断控制器管理PCI设
备的中断请求,因此该寄存器没有意义。即使在x86处理器系统中,如果使用I/O
APIC中断控制器,该寄存器保存的内容仍然无效。目前在绝大多数处理器系统中,
并没有使用该寄存器存放PCI设备使用的中断向量号。
(9) Interrupt Pin寄存器
这个寄存器保存PCI设备使用的中断引脚,PCI总线提供了四个中断引脚INTA#、
INTB#、INTC#和INTD#。Interrupt Pin寄存器为1时表示使用INTA#引脚向中
断控制器提交中断请求,为2表示使用INTB#,为3表示使用INTC#,为4表示使
用INTD#。如果PCI设备只有一个子设备时,该设备只能使用INTA#;如果有多
个子设备时,可以使用INTB~D#信号。如果PCI设备不使用这些中断引脚,向处
理器提交中断请求时,该寄存器的值必须为0。值得注意的是,虽然在PCIe设备
中并不含有INTA~D#信号,但是依然可以使用该寄存器,因为PCIe设备可以使
用INTx中断消息,模拟PCI设备的INTA~D#信号,详见第6.3.4节。
(10) Base Address Register 0~5寄存器
该组寄存器简称为BAR寄存器,BAR寄存器保存PCI设备使用的地址空间的基地址,
该基地址保存的是该设备在PCI总线域中的地址。其中每一个设备最多可以有6个
基址空间,但多数设备不会使用这么多组地址空间。在PCI设备复位之后,该寄存
器将存放PCI设备需要使用的基址空间大小,这段空间是I/O空间还是存储器空间
[5],如果是存储器空间该空间是否可预取,系统软件对PCI总线进行配置时,首先
获得BAR寄存器中的初始化信息,之后根据处理器系统的配置,将合理的基地址写
入相应的BAR寄存器中。系统软件还可以使用该寄存器,获得PCI设备使用的BAR
空间的长度,其方法是向BAR寄存器写入0xFFFF-FFFF,之后再读取该寄存器。
处理器访问PCI设备的BAR空间时,需要使用BAR寄存器提供的基地址。值得注意
的是,处理器使用存储器域的地址,而BAR寄存器存放PCI总线域的地址。因此处
理器系统并不能直接使用“BAR寄存器+偏移”的方式访问PCI设备的寄存器空间,
而需要将PCI总线域的地址转换为存储器域的地址。如果x86处理器系统使能了
IOMMU后,这两个地址也并不一定相等,因此处理器系统直接使用这个PCI总线
域的物理地址,并不能确保访问PCI设备的BAR空间的正确性。除此之外在Linux
系统中,ioremap函数的输入参数为存储器域的物理地址,而不能使用
PCI总线域的物理地址。而在pci_devàresource[bar].start参数中保存的地址已经
经过PCI总线域到存储器域的地址转换,因此在编写Linux系统的设备驱动程序时,
需要使用pci_devàresource[bar].start参数中的物理地址,然后再经过ioremap
函数将物理地址转换为“存储器域”的虚拟地址。
(11) Command寄存器
该寄存器为PCI设备的命令寄存器,该寄存器在初始化时,其值为0,此时这个PCI
设备除了能够接收配置请求总线事务之外,不能接收任何存储器或者I/O请求。系
统软件需要合理设置该寄存器之后,才能访问该设备的存储器或者I/O空间。在
Linux系统中,设备驱动程序调用pci_enable_device函数,使能该寄存器的I/O和
Memory Space位之后,才能访问该设备的存储器或者I/O地址空间。
(12) Status寄存器
该寄存器的绝大多数位都是只读位,保存PCI设备的状态。

linux API

//访问配置空间
int pci_read_config_[byte|word|dword](struct pci_dev *dev, int where, u8 *val)
int pci_write_config_[byte|word|dword](struct pci_dev *dev, int where, u8 *val)
//eg 获取分配给某PCI卡的中断号
u8 irq;
pci_read_config_byte(pdev,PCI_INTERRUPT_LINE,&irq)
//I/O/内存空间操作
unsigned long pci_resource_[start|len|end|flag](pdev,int bar)
eg 操作某PCI卡的控制寄存器,映射到BAR0
unsigned long mmio_base   = pci_resource_start(pdev, bar); 
unsigned long mmio_length = pci_resource_length(pdev, bar);
unsigned long mmio_flags  = pci_resource_flags(pdev, bar); 

void __iomem *buffer; 
if (flags & IORESOURCE_CACHEABLE) { 
  buffer = ioremap(mmio_base, mmio_length); 
} else { 
  buffer = ioremap_nocache(mmio_base, mmio_length); 
}

你可能感兴趣的:(linux驱动开发)