linux下遍历访问PCIE设备配置空间_X86平台

  • 概述
  • 实验环境
  • PCIE 访问流程
  • PCIE总线资源
  • Linux下怎么访问CONFIG_ADDRESS寄存器和CONFIG_DATA寄存器
  • 源代码
  • 运行结果


概述

最近在看王齐老师编写的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设备配置空间
  • centos 抓取pcie插槽 linux查找pcie设备_centos 抓取pcie插槽

  • PCIe 设备配置空间(0x00~0x03F)
  • centos 抓取pcie插槽 linux查找pcie设备_centos 抓取pcie插槽_02

PCIE总线资源

  • PCIE总线数量最大支持256个,每个pcie总线下设备最大支持32个,每个设备的功能最大支持8个
    #define MAX_BUS 256
    #define MAX_DEVIECE 32
    #define MAX_FUNCTION 8

Linux下怎么访问CONFIG_ADDRESS寄存器和CONFIG_DATA寄存器

  • 涉及到的接口函数
  1. iopl() 改变当前进程的I/ O特权级别,在级别 level 指定,这一调用仅适用于i386平台。
    #include <sys/io.h>
    int iopl(int level);
    读写之前set high level 3
    读写完成之后set low level 0
  2. 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);
  3. 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;     
}

运行结果

centos 抓取pcie插槽 linux查找pcie设备_pcie_03