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個字段涉及設備的識別。
- 供應商識別字段(Vendor ID)該字段用一標明設備的製造者。一個有效的供應商標識由PCI SIG來分配,以保證它的唯一性。0FFFFH是該字段的無效值。
- 設備識別字段(Device ID)用以標明特定的設備,具體代碼由供應商來分配。
- 版本識別字段(Revision ID)用來指定一個設備特有的版本識別代碼,其值由供應商提供,可以是0。
- 頭標類型字段(Header Type)該字段有兩個作用,一是用來表示配置空間頭標區第二部分的佈局類型;二是用以指定設備是否包含多功能。位7用來標識一個多功能設備,位7爲0表明是單功能設備,位7爲1表明是多功能設備。位0-位6表明頭標區類型。
- 分類代碼字段(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