BAR寄存器:
- Base Address Register0~5:即BAR寄存器,保存PCI设备使用的地址空间的基地址,保存设备在PCI总线域中的地址,每个设备最多可以有6个基址空间;
- PCI设备复位之后,存放PCI设备需要使用的基地址空间大小,该空间是I /O空间还是存储器空间等信息;
- 软件对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函数将物理地址转换为“存储器域”的虚拟地址,再访问;
PCI标准定义了三种物理地址空间,分别是IO address, Memory address space以及Configuration address space。其中Configuration address space专门用于配置PCI设备,PCI标准规定了标准的configuration space header,每个PCI设备都必须实现,软件通过configuration address space,来访问device的configuration space header,从而可以对device进行配置。
PCI标准要求device将内部寄存器,片上内存等资源map到IO space或者Memory space,从而软件可以通过发起IO/Memory space的读写来访问device的内部资源。PCI标准在configuration space header里预留了6个32-bit的Base address (BAR),系统软件可以通过往BAR里设置IO/Memory space的基地址,来实现对device内部资源的影射。在设置好这些BAR后,一旦软件发起对这些地址的读写,都会被device截获。
PCI规定32位BAR的低4位为read-only,标识了这个device资源在IO space,32-bit的Memory space还是64-bit的Memory space,以及访问时是否prefetchable。BAR的最后一位标识了是IO space还是Memory space。
BAR[bit:0] = 1 --- IO space
= 0 --- Memory space
在OS启动前,BIOS会预先program好device的各个BAR的基地址,所以一般OS不需要重新去program BAR,但是当device资源出现冲突时,系统也可以通过写BAR的值,来重新修改device的资源影射。
PCI标准规定,先往32位BAR里写0xffffffff,再读取该BAR,就可以得到该BAR的size。如果读到的size为0,说明该BAR没有被device使用。
Power-up software can determine how much address space the device requires by writing a
value of all 1's to the register and then reading the value back. The device will return 0's in
all don't-care address bits, effectively specifying the address space required. Unimplemented
Base Address registers are hardwired to zero.
如果该BAR是64-bit Memory space,则下一个BAR被认为是64位的高32位,64位size的高32位,也可以通过同样的方法得到。
64-bit (memory) Base Address registers can be handled the same, except that the second
32-bit register is considered an extension of the first; i.e., bits 32-63. Software writes
0FFFFFFFFh to both registers, reads them back, and combines the result into a 64-bit value.
Size calculation is done on the 64-bit value.
读取base,size以及type的伪代码: u32 base, size; pci_resource_type type; (io_space, mem32_space, mem64_space) boolean prefetchable; //得到base address pci_config_read(device, BARi, &base); //得到size pci_config_write(device, BAR, 0xffffffff); pci_config_read(device, BAR, &size); //重新写回base address,有需要也可以是其他的地址 pci_config_write(device, BAR, base); //确定type if (base & 0x1) type = io_space; else if((base & 0x6) == 0x4) type = mem64_space; else type = mem32_space; prefetchable = (base & 0x8) ? true : false; //如果是64-bit Memory space,得到base address和size的高32位,方法相同 if (type == mem64_space) { u64 base64 = base; u64 size64 = size; //BAR+4指向下一个BAR的位置,此时被认为是64位的高32位 pci_config_read(device, BAR+4, &base); base64 |= ((u64)base << 32); pci_config_write(device, BAR+4, 0xffffffff); pci_config_read(device, BAR+4, &size); size64 |= ((u64)size << 32); }