讀取PCI配置空間數據並操作其映射的物理內存

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值操作物理內存。
#include <sys/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define PCI_CONFIG_ADDR(bus, dev, fn, reg) (0x80000000 | (bus << 16) | (dev << 11) | (fn << 8) | (reg & ~3))

void usage()
{
    printf("Usage: readpci [-bdfrth]\n\
            -a   addr  :   specify bar address default 0\n\
            -b   bus   :   specify   PCI   bus   number(default   0)\n\
            -d   dev   :   device   number(default   0)\n\
            -f   fn    :   function   number(default   0)\n\
            -r   reg   :   register   address(must   be   multiple   of   4,   default   0)\n\
            -p   port  :   specify port number default 0\n\
            -v   value :   write a integer value into the address\n\
            -h         :   print   this   help   text\n ");
    exit(-1);
}

int operaMem(int bar, int offset, int modValue, int isWrite)
{
	int i;
	int fd;
	char* mem;
	//open /dev/mem with read and write mode
	if((fd = open ("/dev/mem", O_RDWR)) < 0)
	{
		perror ("open error\n");
		return -1;
	}

	//map physical memory 0-10 bytes
	mem = mmap (0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, bar);
	if (mem == MAP_FAILED)
	{
		perror ("mmap error:\n");
		return 1;
	}

	int value = *((int *)(mem + offset));
    printf("The value at 0x%x is 0x%x\n", bar + offset, value);
    if(isWrite == 1)
    {
        printf("Write value 0x%x at 0x%x\n", modValue, bar + offset);
        memcpy(mem + offset, &modValue, 4);
        printf("Reread the value at 0x%x is 0x%x\n", bar + offset, *((int *)(mem + offset)));
    }
    
	munmap (mem, 4096); //destroy map memory
	close (fd);         //close file
	return 0;
}

int parseInt(char *str)
{
    int value = 0;

    char tmpChar;
    while((tmpChar = *str) != '\0')
    {
        if(tmpChar >= 'A' && tmpChar <= 'F')
        {
            tmpChar = tmpChar - 'A' + 10;
        }
        else if(tmpChar >= 'a' && tmpChar <= 'f')
        {
            tmpChar = tmpChar - 'a' + 10;
        }
        else
        {
            tmpChar -= '0';
        }
        value = value * 16 + tmpChar;
        str += 1;
    }
    return value;
}

int main(int argc, char **argv)
{
    unsigned long val = 0;
    char options[] = "a:b:d:f:r:v:p:h";
    int addr = 0, bus = 0, dev = 0, fn = 0, reg = 0, port = 0;
    int opt, value = 0, isWrite = 0, isReadBar = 0;
    while((opt = getopt(argc, argv, options)) != -1)
    {
        switch(opt)
        {
            case 'a':
                addr = parseInt(optarg);
                break;
            case 'b':
                bus = atoi(optarg);
                break;
            case 'd':
                dev = atoi(optarg);
                break;
            case 'f':
                fn = atoi(optarg);
                break;
            case 'r':
                reg = parseInt(optarg);
                break;
            case 'p':
                port = atoi(optarg);
                isReadBar = 1;
                break;
            case 'v':
                value = parseInt(optarg);
                isWrite = 1;
                break;
            case 'h':
            default:
                usage();
                break;
        }
    }

    iopl(3);
    if(isWrite == 0)
    {
        if(isReadBar == 0)
        {
            int i;
            for(i = 0; i < 256; i += 4)
            {
                outl(PCI_CONFIG_ADDR(bus, dev, fn, i), 0xCF8);
                val = inl(0xCFC);
                printf("PCI:Bus %d, DEV %d, FUNC %d, REG %x, Value is %x\n ", bus, dev, fn, i, val);
            }
        }
        else
        {
            outl(PCI_CONFIG_ADDR(bus, dev, fn, 0x10), 0xCF8);
            val = inl(0xCFC) & 0xfffffff0;
            printf("The base address value is 0x%x\n", val);
            int pointAddr = val + port * 0x1000;
            printf("The offset address value is 0x%x\n", pointAddr + addr);
            operaMem(pointAddr, addr, 0, 0);
        }
    }
    else
    {
        outl(PCI_CONFIG_ADDR(bus, dev, fn, 0x10), 0xCF8);
        val = inl(0xCFC) & 0xfffffff0;
        printf("The base address value is 0x%x\n", val);
        int pointAddr = val + port * 0x1000;
        printf("The offset address value is 0x%x\n", pointAddr + addr);
        operaMem(pointAddr, addr, value, 1);
    }
    return 0;
}

來自:http://blog.csdn.net/zsf8701/article/details/7817661
發佈了45 篇原創文章 · 獲贊 10 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章