如何访问PCI配置空间数据并操作其映射的物理内存

来源:互联网 发布:弹丸论破2知乎 编辑:程序博客网 时间:2024/05/17 08:50

PC机在启动的时候,都会看到一个PCI设备清单,可以看到机器中的所有PCI设备,其实搜索PCI设备的程序并不难编,本文通过一个实例说明如何遍历PCI设备。

1、了解PCI设备

       PCI的含义是外设部件互连(Peripheral Component Interconnect),PCI局部总线(Local Bus)是1991年由Intel定义的,现在PCI局部总线已经成为了PC机中不可缺少的外围设备总线,几乎所有的外部设备都连接到PCI局部总线上, 我们说的PCI设备,实际上就是指连接在PCI局部总线上的设备。

2、了解PCI配置空间

       学习PCI编程,不了解PCI的配置空间是不可能的,配置空间是一块容量为256字节并具有特定记录结构或模型的地址空间,通过配置空间,我们可以了解该PCI设备的一些配置情况,进而控制该设备,除主总线桥以外的所有PCI设备都必须事先配置空间,本节仅就一些配置空间的共有的规定作一些说明,更加具体和详细的信息请参阅其他书籍及相应的芯片手册。
       配置空间的前64个字节叫头标区,头标区又分成两个部分,第一部分为前16个字节,在各种类型的设备中定义都是一样的,其他字节随各设备支持的功能不同而有所不同,位于偏移0EH的投标类型字段规定了是何种布局,目前有三种头标类型,头标类型1用于PCI-PCI桥,头标类型2用于PCI-CARDBUS 桥,头标类型0用于其他PCI设备,下图为头标类型0的头标区布局。
DW |    Byte3    |    Byte2    |    Byte1    |     Byte0     | Addr---+---------------------------------------------------------+----- 0 |     Device ID     |     Vendor ID      | 00---+---------------------------------------------------------+----- 1 |      Status     |      Command      | 04---+---------------------------------------------------------+----- 2 |        Class Code        | Revision ID | 08---+---------------------------------------------------------+----- 3 |   BIST  | Header Type | Latency Timer | Cache Line  | 0C---+---------------------------------------------------------+----- 4 |           Base Address 0           | 10---+---------------------------------------------------------+----- 5 |           Base Address 1           | 14---+---------------------------------------------------------+----- 6 |           Base Address 2           | 18---+---------------------------------------------------------+----- 7 |           Base Address 3           | 1C---+---------------------------------------------------------+----- 8 |           Base Address 4           | 20---+---------------------------------------------------------+----- 9 |           Base Address 5           | 24---+---------------------------------------------------------+-----10 |          CardBus CIS pointer          | 28---+---------------------------------------------------------+-----11 |  Subsystem Device ID  |   Subsystem Vendor ID   | 2C---+---------------------------------------------------------+-----12 |        Expansion ROM Base Address        | 30---+---------------------------------------------------------+-----13 |        Reserved(Capability List)         | 34---+---------------------------------------------------------+-----14 |            Reserved             | 38---+---------------------------------------------------------+-----15 |  Max_Lat  |  Min_Gnt  |  IRQ Pin  |  IRQ Line  | 3C-------------------------------------------------------------------
       头标区中有5个字段涉及设备的识别。
  1. 供应商识别字段(Vendor ID)该字段用一标明设备的制造者。一个有效的供应商标识由PCI SIG来分配,以保证它的唯一性。0FFFFH是该字段的无效值。
  2. 设备识别字段(Device ID)用以标明特定的设备,具体代码由供应商来分配。
  3. 版本识别字段(Revision ID)用来指定一个设备特有的版本识别代码,其值由供应商提供,可以是0。
  4. 头标类型字段(Header Type)该字段有两个作用,一是用来表示配置空间头标区第二部分的布局类型;二是用以指定设备是否包含多功能。位7用来标识一个多功能设备,位7为0表明是单功能设备,位7为1表明是多功能设备。位0-位6表明头标区类型。
  5.  分类代码字段(Class Code)标识设备的总体功能和特定的寄存器级编程接口。该字节分三部分,每部分占一个字节,第一部分是基本分类代码,位于偏移0BH,第二部分叫子分类代码,位于偏移0AH处,第三部分用于标识一个特定的寄存器级编程接口(如果有的话)。

3、配置空间寄存器的读写

       x86的CPU只有内存和I/O两种空间,没有专用的配置空间,PCI协议规定利用特定的I/O空间操作驱动PCI桥路转换成配置空间的操作。目前存在两种转换机制,即配置机制1#和配置机制2#。配置机制2#在新的设计中将不再被采用,新的设计应使用配置机制1#来产生配置空间的物理操作。这种机制使用了两个特定的32位I/O空间,即CF8h和CFCh。这两个空间对应于PCI桥路的两个寄存器,当桥路看到CPU在局部总线对这两个I/O空间进行双字 操作时,就将该I/O操作转变为PCI总线的配置操作。寄存器CF8h用于产生配置空间的地址(CONFIG-ADDRESS),寄存器CFCh用于保存 配置空间的读写数据(CONFIG-DATA)。
将要访问配置空间寄存器的总线号、设备号、功能号和寄存器号以一个双字的格式写到配置地址端口 (CF8H-CFBH),接着执行配置数据端口(CFCH)的读和写,向配置数据口写数据即向配置空间写数据,从配置数据口读数据即从配置空间读数据。
配置地址端口(CF8H)的格式定义如下:
        寄存器号:选择配置空间中的一个双字(32位)        功能号:  选择多功能设备中的某一个功能,有八种功能,0--7        设备号:  在一条给定的总线上选择32个设备中的一个。0--31        总线号:  从系统中的256条总线中选择一条,0--255
        尽管理论上可以有256条总线,但实际上PC机上PCI插槽的总线号都是1,有些工控机的总线号是2或者3,所以我们只需要查找0--4号总线就足够了。PCI规范规定,功能0是必须实现的,所以,如果功能0的头标类型字段的位7为0,表明这是一个单功能设备,则没有必要再去查其他功能,否则要查询所有其他功能。

4、读取PCI设备

       至此,我们掌握的有关PCI的知识已经足够我们遍历PCI设备了,其实便利方法非常简单就是按照总线号、设备号、功能号的顺序依次罗列所有的可能性,读取配置空间头标区的供应商代码、及设备代码,进而找到所有PCI设备。下面是一个例子,关于读取指定PCI设备的指定配置空间数据的方法,同时可以根据读取的BAR值操作物理内存。
[cpp] view plaincopyprint?
  1. #include <sys/io.h>  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4. #include <getopt.h>  
  5. #include <unistd.h>  
  6. #include <sys/mman.h>  
  7. #include <sys/types.h>  
  8. #include <sys/stat.h>  
  9. #include <fcntl.h>  
  10. #include <string.h>  
  11.   
  12. #define PCI_CONFIG_ADDR(bus, dev, fn, reg) (0x80000000 | (bus << 16) | (dev << 11) | (fn << 8) | (reg & ~3))  
  13.   
  14. void usage()  
  15. {  
  16.     printf("Usage: readpci [-bdfrth]\n\  
  17.             -a   addr  :   specify bar address default 0\n\  
  18.             -b   bus   :   specify   PCI   bus   number(default   0)\n\  
  19.             -d   dev   :   device   number(default   0)\n\  
  20.             -f   fn    :   function   number(default   0)\n\  
  21.             -r   reg   :   register   address(must   be   multiple   of   4,   default   0)\n\  
  22.             -p   port  :   specify port number default 0\n\  
  23.             -v   value :   write a integer value into the address\n\  
  24.             -h         :   print   this   help   text\n ");  
  25.     exit(-1);  
  26. }  
  27.   
  28. int operaMem(int bar, int offset, int modValue, int isWrite)  
  29. {  
  30.     int i;  
  31.     int fd;  
  32.     char* mem;  
  33.     //open /dev/mem with read and write mode  
  34.     if((fd = open ("/dev/mem", O_RDWR)) < 0)  
  35.     {  
  36.         perror ("open error\n");  
  37.         return -1;  
  38.     }  
  39.   
  40.     //map physical memory 0-10 bytes  
  41.     mem = mmap (0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, bar);  
  42.     if (mem == MAP_FAILED)  
  43.     {  
  44.         perror ("mmap error:\n");  
  45.         return 1;  
  46.     }  
  47.   
  48.     int value = *((int *)(mem + offset));  
  49.     printf("The value at 0x%x is 0x%x\n", bar + offset, value);  
  50.     if(isWrite == 1)  
  51.     {  
  52.         printf("Write value 0x%x at 0x%x\n", modValue, bar + offset);  
  53.         memcpy(mem + offset, &modValue, 4);  
  54.         printf("Reread the value at 0x%x is 0x%x\n", bar + offset, *((int *)(mem + offset)));  
  55.     }  
  56.       
  57.     munmap (mem, 4096); //destroy map memory  
  58.     close (fd);         //close file  
  59.     return 0;  
  60. }  
  61.   
  62. int parseInt(char *str)  
  63. {  
  64.     int value = 0;  
  65.   
  66.     char tmpChar;  
  67.     while((tmpChar = *str) != '\0')  
  68.     {  
  69.         if(tmpChar >= 'A' && tmpChar <= 'F')  
  70.         {  
  71.             tmpChar = tmpChar - 'A' + 10;  
  72.         }  
  73.         else if(tmpChar >= 'a' && tmpChar <= 'f')  
  74.         {  
  75.             tmpChar = tmpChar - 'a' + 10;  
  76.         }  
  77.         else  
  78.         {  
  79.             tmpChar -= '0';  
  80.         }  
  81.         value = value * 16 + tmpChar;  
  82.         str += 1;  
  83.     }  
  84.     return value;  
  85. }  
  86.   
  87. int main(int argc, char **argv)  
  88. {  
  89.     unsigned long val = 0;  
  90.     char options[] = "a:b:d:f:r:v:p:h";  
  91.     int addr = 0, bus = 0, dev = 0, fn = 0, reg = 0, port = 0;  
  92.     int opt, value = 0, isWrite = 0, isReadBar = 0;  
  93.     while((opt = getopt(argc, argv, options)) != -1)  
  94.     {  
  95.         switch(opt)  
  96.         {  
  97.             case 'a':  
  98.                 addr = parseInt(optarg);  
  99.                 break;  
  100.             case 'b':  
  101.                 bus = atoi(optarg);  
  102.                 break;  
  103.             case 'd':  
  104.                 dev = atoi(optarg);  
  105.                 break;  
  106.             case 'f':  
  107.                 fn = atoi(optarg);  
  108.                 break;  
  109.             case 'r':  
  110.                 reg = parseInt(optarg);  
  111.                 break;  
  112.             case 'p':  
  113.                 port = atoi(optarg);  
  114.                 isReadBar = 1;  
  115.                 break;  
  116.             case 'v':  
  117.                 value = parseInt(optarg);  
  118.                 isWrite = 1;  
  119.                 break;  
  120.             case 'h':  
  121.             default:  
  122.                 usage();  
  123.                 break;  
  124.         }  
  125.     }  
  126.   
  127.     iopl(3);  
  128.     if(isWrite == 0)  
  129.     {  
  130.         if(isReadBar == 0)  
  131.         {  
  132.             int i;  
  133.             for(i = 0; i < 256; i += 4)  
  134.             {  
  135.                 outl(PCI_CONFIG_ADDR(bus, dev, fn, i), 0xCF8);  
  136.                 val = inl(0xCFC);  
  137.                 printf("PCI:Bus %d, DEV %d, FUNC %d, REG %x, Value is %x\n ", bus, dev, fn, i, val);  
  138.             }  
  139.         }  
  140.         else  
  141.         {  
  142.             outl(PCI_CONFIG_ADDR(bus, dev, fn, 0x10), 0xCF8);  
  143.             val = inl(0xCFC) & 0xfffffff0;  
  144.             printf("The base address value is 0x%x\n", val);  
  145.             int pointAddr = val + port * 0x1000;  
  146.             printf("The offset address value is 0x%x\n", pointAddr + addr);  
  147.             operaMem(pointAddr, addr, 0, 0);  
  148.         }  
  149.     }  
  150.     else  
  151.     {  
  152.         outl(PCI_CONFIG_ADDR(bus, dev, fn, 0x10), 0xCF8);  
  153.         val = inl(0xCFC) & 0xfffffff0;  
  154.         printf("The base address value is 0x%x\n", val);  
  155.         int pointAddr = val + port * 0x1000;  
  156.         printf("The offset address value is 0x%x\n", pointAddr + addr);  
  157.         operaMem(pointAddr, addr, value, 1);  
  158.     }  
  159.     return 0;  
  160. }  
0 0