1. 程式人生 > >linux環境下遍歷PCI裝置

linux環境下遍歷PCI裝置

終於有時間寫部落格啦,讓我把想寫的都來說清楚!在網上找相關資料發現比較少,所以完成後迫不及待分享給大家,希望能帶給大家幫助,歡迎批評指正!

瞭解PCI匯流排

PCI是Peripheral Component Interconnect(外設部件互連標準)的縮寫,它是目前個人電腦中使用最為廣泛的介面,幾乎所有的主機板產品上都帶有這種插槽。PCI插槽也是主機板帶有最多數量的插槽型別,在目前流行的桌上型電腦主機板上,ATX結構的主機板一般帶有5~6個PCI插槽,而小一點的MATX主機板也都帶有2~3個PCI插槽,可見其應用的廣泛性。

PCI作為處理器系統的區域性匯流排,主要目的為了連線外部裝置,而不是作為處理器的系統匯流排連線cache和Main Memory

host主橋作為連線處理器和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訪問