终于有时间写博客啦,让我把想写的都来说清楚!在网上找相关资料发现比较少,所以完成后迫不及待分享给大家,希望能带给大家帮助,欢迎批评指正!
了解PCI总线
PCI是Peripheral Component Interconnect(外设部件互连标准)的缩写,它是目前个人电脑中使用最为广泛的接口,几乎所有的主板产品上都带有这种插槽。PCI插槽也是主板带有最多数量的插槽类型,在目前流行的台式机主板上,ATX结构的主板一般带有5~6个PCI插槽,而小一点的MATX主板也都带有2~3个PCI插槽,可见其应用的广泛性。
PCI设备的配置空间
访问PCI设备的方式
x86处理器定义了两个IO端口寄存器,用来访问PCI设备的配置空间,即CONFIG_ADDRESS和CONFIG_DATA
CONFIG_ADDRESS地址是0xcf8,也就是说通过访问0xcf8就可以访问CONFIG_ADDRESS寄存器啦,CONFIG_ADDRESS寄存器用来存放PCI设备的ID号
CONFIG_DATA地址是0xcfc,CONFIG_DATA用来存放进行配置读写的数据
那这两个寄存器怎么用呢,简单来说就是把要找的PCI设备的相关信息按CONFIG_ADDRESS寄存器的格式写入0xcf8,然后再去CONFIG_DATA即0xcfc读出来就好了(当然是用宏定义来实现寄存器的地址啦)
下面来看CONFIG_ADDR寄存器的结构
enable位,即第31bit,置为1
bus number 记录PCI设备总线号
device number记录PCI设备设备号
function number记录PCI设备功能号
register number记录PCI设备寄存器号
访问过程:
当X86处理器对CONFIG_DATA寄存器进行I/O读写访问时,并且CONFIG_ADDRESS寄存器的Enable位为1时,Host主桥就把这个读写访问转为PCI配置读写总线事务发往PCI总线,PCI总线根据CONFIG_ADDRESS的ID号,将请求发送到相应的PCI设备寄存器
在这里好不容易搞懂CONFIG_DATA里到底放了些啥,就是说PCI总线根据CONFIG_ADDRESS的bus,device,functin number来寻找PCI设备,找到后再根据CONFIG_ADDRESS的register number寄存器号也就是PCI设备配置空间的偏移量,取出相应的内容放到CONFIG_DATA寄存器,
如register number为0x00即代表PCI设备的vender ID和Device ID,但是在这里我们不需要读取他们的具体内容,只需要知道有就可以了
参考文献:王齐《PCIE体系结构导读》
在上代码之前还需要解释几个函数
1.iopl()系统调用(linux环境)
改变当前进程I/O端口的权能级别。对于允许8514兼容的X服务器在Linux上运行,这一系统调用必不可少。X服务器要求访问所有 65536个I/O端口,ioperm调用不能满足这种需求。另外,为了获取不受限制的I/O端口访问权,以较高级别的I/O权能级运行将允许进程禁止中断。这可能导致系统的崩毁,不推荐那样做。一般用户的I/O访问级是0。本系统调用只应用于i386平台。
用法:
#include <sys/io.h>
参数:
level:新的I/O访问级,范围是[0~3]。
成功执行时,返回0。失败返回-1,errno被设为以下的某个值
EINVAL:参数无效,level大于3
ENOSYS:平台不支持这个系统调用
EPERM:调用进程没有权限使用iopl,要求CAP_SYS_RAWIO权能
2.outl()和inl()
函数原型:
void outl (unsigned int data, unsigned short int port);
static __iniine unsigned int inl(unsigned short int port);
inl(),outl(),是读写端口。
readl(),writel(),是读写内存。
下面来看代码吧(ps:仅适用于linux环境,运行通过,结果正确)
<span style="font-weight: normal;"><span style="font-family:Microsoft YaHei;font-size:14px;">#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/io.h>
#define MAX_BUS 256 //bus bunber:0~255
#define MAX_DEV 32 //device number:0~31
#define MAX_FUN 8 //function number:0~7
#define BASE_ADDR 0x80000000 //基地址,enable=1
#define CONFIG_ADDR 0xcf8
#define CONFIG_DATA 0xcfc
typedef unsigned int WORD; //4byte
int main()
{
WORD bus,dev,fun;
WORD addr,data;
int ret=0;
printf("bus#\tdev#\tfun#\t");
printf("\n");
ret=iopl(3); //set high power
if(ret<0)
{
perror("iopl set high power error");
return -1;
}
for(bus=0;bus<MAX_BUS;++bus)
{
for(dev=0;dev<MAX_DEV;++dev)
{
for(fun=0;fun<MAX_FUN;++fun)
{
addr=BASE_ADDR|(bus<<16)|(dev<<11)|(fun<<8); //把number们分别左移到相应的位上去,register number默认设为0
outl(addr,CONFIG_ADDR); //put addr into CONFIG_ADDR
data=inl(CONFIG_DATA); //read address from CONFIG_DATA
if((data!=0xffffffff)&&(data!=0)) //如果vender ID和Device ID位全是1,代表没有该设备
printf("%2x\t%2x\t%2x\n",bus,dev,fun); //找到PCI设备后打印他的bus,dev,fun number
}
}
}
ret=iopl(0); //set low power
if(ret<0)
{
perror("iopl set low power error");
return -1;
}
return 0;
}</span></span>
下一篇写PCIE设备的mmio访问