PCIE简介
早期的PCI时期,系统为每个PCI设备分配的内存大小仅有256个Bytes。到后来的PCIE时期,随着设备性能增强,PCIE设备的配置空间扩展至4K个Bytes。在这里需要注意:
PCIE一共支持256条Bus,32个Dev,8个Fun。因此在满负载的情况下,共需内存大小 = 4k * 256 328 = 256K Bytes = 256M,这个256M的内存空间是为PCIE设备准备的空间系统不可用,这也是你的内存条实际可用的总是会小于标称的主要原因之一。
PCIE设备发展向前兼容PCI,每个设备的配置空间的前256个Byte是PCI空间,后(4k-256)个Byte的空间是PCIE扩展空间,这是二者的主要区别,另外一个区别就要引出下面的一个问题
PCI/PCIE设备配置空间的访问方式----IO访问 & 内存访问
X86系统中,对PCIE设备配置空间的地址映射一般有两种方式:内存映射和IO映射。因此开发者也可以通过内存访问或者IO访问来访问其配置空间
PCIE设备的访问离不开其Bus,Dev,Fun的编号方式PCIE设备的访问离不开其Bus,Dev,Fun的编号方式,如下图寄存器所示,Bit[23:16]用来存放Bus号,共8Bit,因此解释了上述表述为何一共有256条Bus,Bit[15:11]存放Dev,共5bit可存32个Dev,Bit[10:8]存放Fun,共3Bit可存8个Fun。这也就也是了为何上述PCIE设备数一共是256个Bus,32个Dev和8个Fun。
IO访问:即地址端口0xcf8/数据端口0xcfc,特定Bus,Device,Function按下图方式得到地址(实际中寄存器地址不用偏移两位),写入0xcf8;从0xcfc得到数据。
PCI总线是拓扑结构,PCI总线从0开始,不超过256,Device不超过32,Function不超过8。如下图,挂在总线0,即Bus 0上的为根(root)设备,下面还挂设备的则为桥(Bridge),不再挂设备的即为设备(Device)。挂在桥下的设备总线号必然大于桥的总线号,下图中,PCI桥片1为Bus 0,PCI设备11为bus 1,PCI设备31为Bus 3。所以PCI桥片1的从属总线是1-3。
IO访问
在访问主板上的PCI设备时,首先需要确定PCIE的基地址是多少,一般情况下I/0访问0x80000000L,即第31为1。
进入RU, alt+4选择I/0访问方式,接着输入CF8,此时就可以在此寄存器中访问PCI设备。注意:早期的PCI时期,系统为每个PCI设备分配的内存大小仅有256个Bytes。到后来的PCIE时期,随着设备性能增强, PCIE设备的配置空间扩展至4K个Bytes。 PCIE设备发展向前兼容PCI,每个设备的配置空间的前256个Byte是PCI空间,后(256-4k)个Byte的空间是PCIE扩展空间,而I/0访问只能访问到前256个Byte (字节),若想访问后256-4个Byte,需要使用MIMO 访问(Type选择memory)。按F7可使其显示为32bit位
如何访问某一个PCI设备:首先需要确定访问的PCI设备的Bus号, Device号, fun号,RU下按F6查看,例如Bus=01, Device=00, fun=00,通过下面的计算公式求得该PCI设备的访问地址,addr = Ox80000000 L | (bus<<16) | (dev<<11) | (fun<<8)|
通过计算可知Bus=01, Device=00, fun=00 的PCI设备的地址为: Ox80010000,最后将0x80010000写入CF8中,即可在CFC中查看该PCI设备中的数据。
使用c进行i/o访问PCI配置空间
#include <stdio.h>
#include <stdlib.h>
#include <sys/io.h>
#include <stdint.h>
#include <unistd.h>
#define CONFIG_ADDRESS 0xCF8 // 配置寄存器地址
#define CONFIG_DATA 0xCFC // 数据寄存器地址
#define BASE_ADDRESS_REGISTER 0x80000000 // BAR地址
#define BUSNUMBER 0x01
#define DEVNUMBER 0x00
#define FUNNUMBER 0x00
/**
* @brief 读取PCI配置空间数据
*
* @param bus bus号
* @param dev dev号
* @param func fun号
* @param offset
* @return uint32_t PCI配置寄存器的32位数据
*/
uint32_t pci_config_read(uint8_t bus, uint8_t slot, uint8_t func, uint16_t offset)
{
uint32_t address;
// 创建配置地址,PCI配置空间地址格式为:1000_0000_0000_0000_0000_0000_0000_0000
address = (uint32_t)(BASE_ADDRESS_REGISTER | bus << 16 | slot << 11 | func << 8 | (offset & 0xfc));
printf("Reading address: %08x\n", address);
// 写入配置地址
outl(address, CONFIG_ADDRESS);
// 读取配置数据
return inl(CONFIG_DATA);
}
int main()
{
// 检查root权限
if (geteuid() != 0)
{
printf("This program requires root privileges!\n");
return 1;
}
// 获取I/O权限
if (iopl(3) < 0)
{
perror("Failed to get I/O permission");
return 1;
}
// IO访问只能访问PCI的256bytes的地址空间,所以只能访问到0xFF
printf("Reading PCI configuration space for bus 1, device 0, function 0:\n");
for (uint16_t offset = 0x000; offset <= 0xff; offset += 4)
{
uint32_t value = pci_config_read(BUSNUMBER, DEVNUMBER, FUNNUMBER, offset);
printf("Offset 0x%02X: 0x%08X\n", offset, value);
// 按字节打印
printf("Bytes: ");
for (int i = 0; i < 4; i++)
{
printf("%02X ", (value >> (i * 8)) & 0xFF);
}
printf("\n\n");
}
// 释放I/O权限
iopl(0);
return 0;
}
编译运行
gcc -o GetBARInfo GetBARInfo.c
./GetBARInfo
MMIO方式
在访问主板上的PCI设备时,首先需要在代码中确定PCIE的基地址是多少,0xF8000000(BIOS代码里查找)。
访问某一个PCIE设备:首先需要确定访问的PCIE设备的Bus号, Device号, fun号,offset,例如Bus=01, Device=00, fun=00,offset=0,通过下面的计算公式求得该PCI设备的访问地址。addr = OxF8000000 L | (bus<<20) | (dev<<15) | (fun<<12)+offset.通过计算可知Bus=01, Device=00, fun=00 ,offset