概述
最近在看王齐老师编写的PCI Express体系结构导读,受益颇深,因此打算编程实现写小程序,加深对PCIE知识的理解。动手实践理解更深刻。
本文章参考此博客: linux环境下遍历PCI设备,在此基础上实现了对PCIE 0x00~0x3f配置空间中的读取,打印输出基本和linux 自带lspci -x 命令形式一致。
实验环境
wimdow7系统,Intel 64位处理器
VMware虚拟机 ubuntu14.04
PCIE 访问流程
HOST桥通过bus号,device号,和function号访问到PCIE设备的信息,register号是PCIE设备配置空间(PCIe设备配置空间4096KB)中寄存器的偏移。X86处理器这些信息存放在CONFIG_ADDR寄存器中,CONFIG_DATA寄存器保存该地址中的数据。因此可以通过读写这两个寄存器获取到PCIE设备配置空间中的信息,在X86处理器中这两个寄存器的地址为:
ONFIG_ADDRESS地址是0xcf8,也就是说通过访问0xcf8就可以访问CONFIG_ADDRESS寄存器,
CONFIG_DATA地址是0xcfc,CONFIG_DATA用来存放进行配置读写的数据。
注意 X86处理器下CNFIG_ADDRESS的基地址0x80000000
- CONGFIG_ADDRESS寄存器
bit31是使能对PCI Bus CONFIG_DATA的访问;
bit 30~24为保留,为只读,访问时返回值为0;
bit 23~16是Bus号;
bit 15~10是设备号;
bit 10~8是功能号;
bit 7~2是配置空间中的寄存器,单位为DWORD。
bit 1~0为只读,读取时放回为0。
-
PCIE设备配置空间
-
PCIe 设备配置空间(0x00~0x03F)
PCIE总线资源
- PCIE总线数量最大支持256个,每个pcie总线下设备最大支持32个,每个设备的功能最大支持8个
#define MAX_BUS 256
#define MAX_DEVIECE 32
#define MAX_FUNCTION 8
Linux下怎么访问CONFIG_ADDRESS寄存器和CONFIG_DATA寄存器
- 涉及到的接口函数
- iopl() 改变当前进程的I/ O特权级别,在级别 level 指定,这一调用仅适用于i386平台。
#include <sys/io.h>
int iopl(int level);
读写之前set high level 3
读写完成之后set low level 0 - outl()
outb() I/O 上写入 8 位数据 ( 1 字节 );
outw() I/O 上写入 16 位数据 ( 2 字节 );
outl () I/O 上写入 32 位数据 ( 4 字节)。
#include <sys/io.h>
void outb (unsigned char data, unsigned short port);
void outw (unsigned short data, unsigned short port);
void outl (unsigned long data, unsigned short port); - inl()
inl () I/O 上读出 32 位数据 ( 4 字节)。
#include <sys/io.h>
void inl (unsigned short port);
注意 i386处理器 I/O 空间和内存空间的进程的 I/O 空间写入数据方式不同。Port I/O方式只能访问PCI配置空间,而不能访问PCI-E扩展配置空间(257~4096字节),此时只能通过MMIO方式。
源代码
#include <sys/io.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
//define pcie device info limit
#define MAX_BUS 256
#define MAX_DEVICE 32
#define MAX_FUNCTION 8
//define CONFIG_ADDRESS and CONFIG_DATA
#define CONFIG_ADDRESS 0xCF8
#define CONFIG_DATA 0xCFC
#define BASE_ADDR 0x80000000
typedef unsigned int WORD;//4byte
int main()
{
WORD bus,device,func,reg;
WORD data,address;//read info from CONFIG_DATA,address set to CONFIG_ADDRESS
int ret=0;
//unsigned char tmpc;
ret = iopl(3);
if(ret<0)
{
perror("iopl set to high level error\n");
return -1;
}
//printf("bus\tdev\func\n");
for(bus=0;bus<MAX_BUS;bus++)
for(device=0;device<MAX_DEVICE;device++)
for(func=0;func<MAX_FUNCTION;func++)
{
for(reg=0;reg<16;reg++)
address = BASE_ADDR|(bus<<16)|(device<<11)|(func<<8);
outl(address,CONFIG_ADDRESS);//put addr to config_address
data = inl(CONFIG_DATA);//read data from config data;
if((data!=0xffffffff) && (data!=0))
{
printf("\n%02x:%02x:%02x\n",bus,device,func);
for(reg=0;reg<16;reg++)
{
if(reg%4==0)
{
printf("%02x:",reg*4);
}
address = BASE_ADDR|(bus<<16)|(device<<11)|(func<<8)|(reg<<2);
outl(address,CONFIG_ADDRESS);//put addr to config_address
data = inl(CONFIG_DATA);//read data from config data;
printf("%02x ",(unsigned char)(data>>0));
printf("%02x ",(unsigned char)(data>>0));
printf("%02x ",(unsigned char)(data>>0));
printf("%02x ",(unsigned char)(data>>0));
if(reg%4==3)
printf("\n");
}
}
}
iopl(0);
if(ret<0)
{
perror("iopl set to low level error\n");
return -1;
}
return 0;
}