PCIE寄存器访问

发布于:2025-03-16 ⋅ 阅读:(20) ⋅ 点赞:(0)

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