linux環境下遍歷PCI裝置
終於有時間寫部落格啦,讓我把想寫的都來說清楚!在網上找相關資料發現比較少,所以完成後迫不及待分享給大家,希望能帶給大家幫助,歡迎批評指正!
瞭解PCI匯流排
PCI是Peripheral Component Interconnect(外設部件互連標準)的縮寫,它是目前個人電腦中使用最為廣泛的介面,幾乎所有的主機板產品上都帶有這種插槽。PCI插槽也是主機板帶有最多數量的插槽型別,在目前流行的桌上型電腦主機板上,ATX結構的主機板一般帶有5~6個PCI插槽,而小一點的MATX主機板也都帶有2~3個PCI插槽,可見其應用的廣泛性。
PCI作為處理器系統的區域性匯流排,主要目的為了連線外部裝置,而不是作為處理器的系統匯流排連線cache和Main Memoryhost主橋作為連線處理器和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訪問