您的位置:首页 > 运维架构 > Linux

linux环境下遍历PCI设备

2015-08-14 17:13 537 查看
终于有时间写博客啦,让我把想写的都来说清楚!在网上找相关资料发现比较少,所以完成后迫不及待分享给大家,希望能带给大家帮助,欢迎批评指正!

了解PCI总线

PCI是Peripheral
Component Interconnect(外设部件互连标准)的缩写,它是目前个人电脑中使用最为广泛的接口,几乎所有的主板产品上都带有这种插槽。PCI插槽也是主板带有最多数量的插槽类型,在目前流行的台式机主板上,ATX结构的主板一般带有5~6个PCI插槽,而小一点的MATX主板也都带有2~3个PCI插槽,可见其应用的广泛性。

PCI作為处理器系統的局部总线,主要目的为了连接外部设备,而不是作为处理器的系統总线连接cache和Main
Memory

host主桥作为连接处理器和PCI的介质,可以直接推出一条PCI总线,该总线又可以通过一个PCI桥连接其他PCI设备或PCI桥,这点和树有些类似
每一个Host主桥管理一个PCI总线域

下面进入正题

PCI设备的配置空间



vendor
ID代表生产厂商看,0x8086代表Intel
Device
ID是这个厂商生产的具体设备
我们遍历PCI设备就是找到这个配置空间并打印出他的venderID和Device
ID即可

访问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>

int iopl(int level);

ps:必须root权限执行

参数:

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访问

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: