c語言-遍歷pci設備(2)mmio訪問

前言

今天其實我在公司也沒有作什麼,可是昨天就把pcie遍歷的mmio形式作了出來,讚賞公司的臺灣服務器,至少我可使用google來去搜索我想要的資料和答案,有一位大神在臺灣的論壇上發佈了一片博文,針對dos環境下的mmio的方法,在國內經過百度等等方法是沒法訪問到的,固然最讓人失望的是,若是我不開代理,直接輸入網址也是沒法進入的,可能有不少人以爲你遍歷pcie幹嘛?嘿嘿,那就是告訴你如何經過代碼去訪問我門電腦裏面最底層的設備,這是一種極其須要能力的。好了,不扯皮了,小編帶你經過c語言與彙編的模式進入dos環境訪問以及Linux系統的各種方法。php

mmio

可能有不少人對mmio的訪問模式有必定的疑問,那麼如今小編就給你們普及一下,固然有不少資料都是來自於網絡,小編參考了太多的資料,在這裏便不一一道謝了。說到mmio,小編會給你們普及一些概念,特別時4gb地址空間的一些概念,這些概念有助於各位去理解。linux

MMIO(Memory mapping I/O)即內存映射I/O,它是PCI規範的一部分,I/O設備被放置在內存空間而不是I/O空間。從處理器的角度看,內存映射I/O後系統設備訪問起來和內存同樣。這樣訪問AGP/PCI-E顯卡上的幀緩存,BIOS,PCI設備就可使用讀寫內存同樣的彙編指令完成,簡化了程序設計的難度和接口的複雜性。I/O做爲CPU和外設交流的一個渠道,主要分爲兩種,一種是Port I/O,一種是MMIO(Memory mapping I/O)。編程

那麼說到這些必須得提到內存映射,內存映射的機制在咱們想訪問pci-e的機制中是很是有用的。api

內存映射,簡而言之就是將用戶空間的一段內存區域映射到內核空間,映射成功後,用戶對這段內存區域的修改能夠直接反映到內核空間,一樣,內核空間對這段區域的修改也直接反映用戶空間。那麼對於內核空間<---->用戶空間二者之間須要大量數據傳輸等操做的話效率是很是高的。緩存

dos環境的mmio方式遍歷pci-e

在dos環境下,你須要打開保護機制,進行訪問32位的數據格式,在dos是16位訪問的,而後經過相應的彙編及機器碼進行,進行相關的操做,小編對此也不是很是瞭解,你們不妨去參考臺灣那個大神的博客,我也是參考他的博客作出的dos系統下的編程。服務器

#include<stdio.h>
#include<dos.h> 
#define BaseAddr 0xF8000000
typedef unsigned long DWORD;
//open 
void openA20()
{ while(inp(0x64) & 2); outp(0x64,0xd1);
while(inp(0x64) & 2); outp(0x60,0xdf);
while(inp(0x64) & 2); outp(0x64,0xff);
}
unsigned long GDT_def[]={0,0,0x0000FFFF,0x008F9200}; 
unsigned char GDT_Addr[6]={0};
void set4gb() 
{ asm{
    cli
    push ds ; push es
    mov word ptr GDT_Addr[0], (2*8-1) 
    mov eax,ds 
    shl eax,4 
    xor ebx,ebx
    mov bx,offset GDT_def 
    add eax,ebx 
    mov dword ptr GDT_Addr[2],eax 
    lgdt fword ptr GDT_Addr 
    mov bx,8 
    mov eax,cr0
    or al,1
    mov cr0,eax
    jmp flush1
    }
    flush1: asm{
    mov ds,bx 
    mov es,bx
    and al,0feh
    mov cr0,eax
    jmp flush2
    } 
    flush2: asm{
    pop es ; pop ds
    sti
    }
}
DWORD ReadMemByPM(DWORD addr)
{
    DWORD result;
    _asm{
    push ds;
    mov ax,0;
    mov ds,ax;
    mov esi,addr;
    mov eax,[esi];
    mov result,eax;
    pop ds;
    }
    return result;
}

DWORD ReadMem(DWORD mAddr)
{
    #ifdef DEBUG
    printf("this Memory is 0x%llX\n",mAddr);
    #endif
    DWORD result=0;
    _asm{
        push eax;
        mov eax,[mAddr];
        mov result,eax;
        pop eax;
    }
    return result;
}
int main(){
    DWORD busNum,devNum,funNum,result=0,addr,CabID,mOffset,DevPortType;
    DWORD vendorID,deviceID,LinkWidth,LinkSpeed;
    char* PortType[]={"PCI-E Endpoint","Leg PCIE Endpoint"," Root Complex","Upstream PCI-E Switch","Downstream PCI-E Switch","PCI-E to PCI Bridge","PCI to PCI-E Bridge","Igrd Endpoint","Event Collector","null"},TypeIndex;
    char* SpeedType[]={"2.5G/S","2.5G/S OR 5G/S","2.5G/S OR 5G/S OR 8G/S","NO Speed"},SpeedIndex;
    openA20();
    set4gb();
    printf("busNum devNum  funNum\t vendorID  deviceID  type\tWidth\tSpeed\n");
    for(busNum=0;busNum<16;busNum++)
        for(devNum=0x0;devNum<32;devNum++)
            for(funNum=0;funNum<8;funNum++)
            {
                addr=(0xF8000000)|(busNum<<20)|(devNum<<15)|(funNum<<12);
                result=ReadMemByPM(addr);
                if(result!=0xffffffff)
                {
                    result=ReadMemByPM(addr|0x34);
                    mOffset=result&0x000000ff;
                    LinkWidth=0;
                    LinkSpeed=0;
                    while(1)
                    {
                        result=ReadMemByPM(addr|mOffset);
                        CabID=result&0x000000ff;
                        if(CabID == 0x00000010){
                            result=ReadMemByPM(addr);
                            vendorID=result&0x0000ffff;
                            deviceID=(result&0xffff0000)>>16;
                            result=ReadMemByPM(addr|mOffset);
                            DevPortType=(result&0x00f00000)>>20;
                            switch(DevPortType)
                            {
                                case 0x0:TypeIndex = 0;break;
                                case 0x1:TypeIndex = 1;break;
                                case 0x4:TypeIndex = 2;break;
                                case 0x5:TypeIndex = 3;break;
                                case 0x6:TypeIndex = 4;break;
                                case 0x7:TypeIndex = 5;break;
                                case 0x8:TypeIndex = 6;break;
                                case 0x9:TypeIndex = 7;break;
                                case 0xa:TypeIndex = 8;break;
                                default:TypeIndex = 9;break;
                            }
                            result=ReadMemByPM(addr+mOffset+0x0C);
                            LinkSpeed=(result&0x0000000f);
                            switch(LinkSpeed){
                                case 0x1:SpeedIndex = 0;break;
                                case 0x2:SpeedIndex = 1;break;
                                case 0x3:SpeedIndex = 2;break;
                                default:SpeedIndex = 3;break;
                            }
                            LinkWidth=(result&0x000003f0)>>4;
                            printf("%2.2llx \t%2.2llx\t  %2.2llx\t %4.4llx\t     %4.4llx %s   X%2lu  %s\n",busNum,devNum,funNum,vendorID,deviceID,PortType[TypeIndex],LinkWidth,SpeedType[SpeedIndex]);
                            break;
                        }else{
                        mOffset=(result&0x0000ff00)>>8;
                        if(mOffset==0x00000000)
                         break;
                         }
                    }
                }   
            }   
  return 0; 
}

在這裏須要注意的是你的電腦的基地址並不是是0xF8000000。網絡

linux環境下

提到linux就不得不說Linux下的一個api,mmap.app

mmap系統調用

mmap將一個文件或者其它對象映射進內存。文件被映射到多個頁上,若是文件的大小不是全部頁的大小之和,最後一個頁不被使用的空間將會清零。munmap執行相反的操做,刪除特定地址區域的對象映射。
當使用mmap映射文件到進程後,就能夠直接操做這段虛擬地址進行文件的讀寫等操做,沒必要再調用read,write等系統調用.但需注意,直接對該段內存寫時不會寫入超過當前文件大小的內容.this

採用共享內存通訊的一個顯而易見的好處是效率高,由於進程能夠直接讀寫內存,而不須要任何數據的拷貝。對於像管道和消息隊列等通訊方式,則須要在內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次數據:一次從輸入文件到共享內存區,另外一次從共享內存區到輸出文件。實際上,進程之間在共享內存時,並不老是讀寫少許數據後就解除映射,有新的通訊時,再從新創建共享內存區域。而是保持共享區域,直到通訊完畢爲止,這樣,數據內容一直保存在共享內存中,並無寫回文件。共享內存中的內容每每是在解除映射時才寫回文件的。所以,採用共享內存的通訊方式效率是很是高的。google

基於文件的映射,在mmap和munmap執行過程的任什麼時候刻,被映射文件的st_atime可能被更新。若是st_atime字段在前述的狀況下沒有獲得更新,首次對映射區的第一個頁索引時會更新該字段的值。用PROT_WRITE 和 MAP_SHARED標誌創建起來的文件映射,其st_ctime 和 st_mtime在對映射區寫入以後,但在msync()經過MS_SYNC 和 MS_ASYNC兩個標誌調用以前會被更新。
用法:

void mmap(void start, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *start, size_t length);
返回說明:
成功執行時,mmap()返回被映射區的指針,munmap()返回0。失敗時,mmap()返回MAP_FAILED[其值爲(void *)-1],munmap返回-1。errno被設爲如下的某個值

  • EACCES:訪問出錯
  • EAGAIN:文件已被鎖定,或者太多的內存已被鎖定
  • EBADF:fd不是有效的文件描述詞
  • EINVAL:一個或者多個參數無效
  • ENFILE:已達到系統對打開文件的限制
  • ENODEV:指定文件所在的文件系統不支持內存映射
  • ENOMEM:內存不足,或者進程已超出最大內存映射數量
  • EPERM:權能不足,操做不容許
  • ETXTBSY:已寫的方式打開文件,同時指定MAP_DENYWRITE標誌
  • SIGSEGV:試着向只讀區寫入
  • SIGBUS:試着訪問不屬於進程的內存區
    參數:
  • start:映射區的開始地址。
  • length:映射區的長度。
  • prot:指望的內存保護標誌,不能與文件的打開模式衝突。是如下的某個值,能夠經過or運算合理地組合在一塊兒
  • PROT_EXEC //頁內容能夠被執行
  • PROT_READ //頁內容能夠被讀取
  • PROT_WRITE //頁能夠被寫入
  • PROT_NONE //頁不可訪問

  • flags:指定映射對象的類型,映射選項和映射頁是否能夠共享。它的值能夠是一個或者多個如下位的組合體

    • MAP_FIXED //使用指定的映射起始地址,若是由start和len參數指定的內存區重疊於現存的映射空間,重疊部分將會被丟棄。若是指定的起始地址不可用,操做將會失敗。而且起始地址必須落在頁的邊界上。
  • MAP_SHARED //與其它全部映射這個對象的進程共享映射空間。對共享區的寫入,至關於輸出到文件。直到msync()或者munmap()被調用,文件實際上不會被更新。
  • MAP_PRIVATE //創建一個寫入時拷貝的私有映射。內存區域的寫入不會影響到原文件。這個標誌和以上標誌是互斥的,只能使用其中一個。
  • MAP_DENYWRITE //這個標誌被忽略。
  • MAP_EXECUTABLE //同上
  • MAP_NORESERVE //不要爲這個映射保留交換空間。當交換空間被保留,對映射區修改的可能會獲得保證。當交換空間不被保留,同時內存不足,對映射區的修改會引發段違例信號。
  • MAP_LOCKED //鎖定映射區的頁面,從而防止頁面被交換出內存。
  • MAP_GROWSDOWN //用於堆棧,告訴內核VM系統,映射區能夠向下擴展。
  • MAP_ANONYMOUS //匿名映射,映射區不與任何文件關聯。
  • MAP_ANON //MAP_ANONYMOUS的別稱,再也不被使用。
  • MAP_FILE //兼容標誌,被忽略。
  • MAP_32BIT //將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略。當前這個標誌只在x86-64平臺上獲得支持。
  • MAP_POPULATE //爲文件映射經過預讀的方式準備好頁表。隨後對映射區的訪問不會被頁違例阻塞。
  • MAP_NONBLOCK //僅和MAP_POPULATE一塊兒使用時纔有意義。不執行預讀,只爲已存在於內存中的頁面創建頁表入口。

  • fd:有效的文件描述詞。若是MAP_ANONYMOUS被設定,爲了兼容問題,其值應爲-1。

offset:被映射對象內容的起點。

系統調用munmap()

int munmap( void * addr, size_t len )
該調用在進程地址空間中解除一個映射關係,addr是調用mmap()時返回的地址,len是映射區的大小。當映射關係解除後,對原來映射地址的訪問將致使段錯誤發生。

系統調用msync()

int msync ( void * addr , size_t len, int flags)
通常說來,進程在映射空間的對共享內容的改變並不直接寫回到磁盤文件中,每每在調用munmap()後才執行該操做。能夠經過調用msync()實現磁盤上文件內容與共享內存區的內容一致。

系統調用mmap()用於共享內存的兩種方式:

(1)使用普通文件提供的內存映射:適用於任何進程之間;此時,須要打開或建立一個文件,而後再調用mmap();典型調用代碼以下:

fd=open(name, flag, mode);
if(fd<0)
...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);
經過mmap()實現共享內存的通訊方式有許多特色和要注意的地方

(2)使用特殊文件提供匿名內存映射:適用於具備親緣關係的進程之間;因爲父子進程特殊的親緣關係,在父進程中先調用mmap(),而後調用fork()。那麼在調用fork()以後,子進程繼承父進程匿名映射後的地址空間,一樣也繼承mmap()返回的地址,這樣,父子進程就能夠經過映射區域進行通訊了。注意,這裏不是通常的繼承關係。通常來講,子進程單獨維護從父進程繼承下來的一些變量。而mmap()返回的地址,卻由父子進程共同維護。
對於具備親緣關係的進程實現共享內存最好的方式應該是採用匿名內存映射的方式。此時,沒必要指定具體的文件,只要設置相應的標誌便可.

mmap進行內存映射的原理

mmap系統調用的最終目的是將,設備或文件映射到用戶進程的虛擬地址空間,實現用戶進程對文件的直接讀寫,這個任務能夠分爲如下三步:

在用戶虛擬地址空間中尋找空閒的知足要求的一段連續的虛擬地址空間,爲映射作準備(由內核mmap系統調用完成)每一個進程擁有3G字節的用戶虛存空間。可是,這並不意味着用戶進程在這3G的範圍內能夠任意使用,由於虛存空間最終得映射到某個物理存儲空間(內存或磁盤空間),才真正可使用。

那麼,內核怎樣管理每一個進程3G的虛存空間呢?歸納地說,用戶進程通過編譯、連接後造成的映象文件有一個代碼段和數據段(包括data段和bss段),其中代碼段在下,數據段在上。數據段中包括了全部靜態分配的數據空間,即全局變量和全部申明爲static的局部變量,這些空間是進程所必需的基本要求,這些空間是在創建一個進程的運行映像時就分配好的。除此以外,堆棧使用的空間也屬於基本要求,因此也是在創建進程時就分配好的.

在內核中,這樣每一個區域用一個結構struct vm_area_struct 來表示.它描述的是一段連續的、具備相同訪問屬性的虛存空間,該虛存空間的大小爲物理內存頁面的整數倍。可使用 cat /proc//maps來查看一個進程的內存使用狀況,pid是進程號.其中顯示的每一行對應進程的一個vm_area_struct結構.
下面是struct vm_area_struct結構體的定義:

#include <linux/mm_types.h>

/* This struct defines a memory VMM memory area. */

struct vm_area_struct {
struct mm_struct * vm_mm; /* VM area parameters */
unsigned long vm_start;
unsigned long vm_end;

/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot;
unsigned long vm_flags;

/* AVL tree of VM areas per task, sorted by address */
short vm_avl_height;
struct vm_area_struct * vm_avl_left;
struct vm_area_struct * vm_avl_right;

/* For areas with an address space and backing store,
vm_area_struct *vm_next_share;
struct vm_area_struct **vm_pprev_share;
struct vm_operations_struct * vm_ops;
unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */
struct file * vm_file;
unsigned long vm_raend;
void * vm_private_data; /* was vm_pte (shared mem) */
};

一般,進程所使用到的虛存空間不連續,且各部分虛存空間的訪問屬性也可能不一樣。因此一個進程的虛存空間須要多個vm_area_struct結構來描述。在vm_area_struct結構的數目較少的時候,各個vm_area_struct按照升序排序,以單鏈表的形式組織數據(經過vm_next指針指向下一個vm_area_struct結構)。可是當vm_area_struct結構的數據較多的時候,仍然採用鏈表組織的化,勢必會影響到它的搜索速度。針對這個問題,vm_area_struct還添加了vm_avl_hight(樹高)、vm_avl_left(左子節點)、vm_avl_right(右子節點)三個成員來實現AVL樹,以提升vm_area_struct的搜索速度。

  假如該vm_area_struct描述的是一個文件映射的虛存空間,成員vm_file便指向被映射的文件的file結構,vm_pgoff是該虛存空間起始地址在vm_file文件裏面的文件偏移,單位爲物理頁面。

所以,mmap系統調用所完成的工做就是準備這樣一段虛存空間,並創建vm_area_struct結構體,將其傳給具體的設備驅動程序.
下面分享的是我寫的linux的mmio遍歷。

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/mman.h>
//#include<linux/pci.h>

//#define DEBUG1

#define PCI_MAX_BUS 2
#define PCI_MAX_DEV 31
#define PCI_MAX_FUN 7
#define PCI_BASE_ADDR 0xfc000000L
#define LEN_SIZE sizeof(unsigned long)
#define SIZE 4096


typedef unsigned int WORD;
typedef unsigned long DWORD;
typedef unsigned char BYTE;


                    
                            
void typeshow(BYTE Type){  
    switch(Type){
      case 0x0f:{
               printf("endpoint\t");
               break;
                 }
      case 0x1f:{
             printf("legacy endpoint\t");
                 break;
               }
      case 0x4f:{
             printf("root complex\t");
                break;
              }
      case 0x5f:{
             printf("upstream pci-e switch\t");
             break;
                }
      case 0x6f:{
             printf("downstream pci-e switch\t");
             break;
             }
      case 0x7f:{
            printf("pci-e to pci bridge\t");
             break;
              }
      case 0x8f:{
             printf("pci to pci-e bridge\t");
             break;
              }
     case 0x9f:{
             printf("root co inte endpoint\t");
             break;
               }
     case 0xaf:{
             printf("root co event endpoint\t");
             break;
                }
     defult:{
             printf("b\t");
             break;     
            }
      }
       
}

void speedshow(int speed){
    if(speed == 0x0){
            printf("speed is 2.5G/s\t");
         }
    else if(speed == 0x2){
            printf("speed is 5G/s\t");
         }
    else if(speed == 0x4){
            printf("speed is 8G/s\t");
         }
    else{
           printf("no speed!\t");
        }
}
void linkspeedshow(int flag){
     switch(flag){
         case 0x1:{
            printf("link speed 0\t");
            break;
              }
         case 0x2:{
            printf("link speed 1\t");
            break;
              }
         case 0x3:{
            printf("link speed 2\t");
            break;
              }
         case 0x4:{
            printf("link speed 3\t");
            break;
              }
         case 0x5:{
            printf("link speed 4\t");
            break;
              }
         case 0x6:{
            printf("link speed 5\t");
            break;
              }
         case 0x7:{
            printf("link speed 6\t");
            break;
              }
         default:
               {
             printf("no speed!\t");
              break;
              }
     }
}
void linkwidthshow(int flag){
    printf("linkwidth:");
    switch(flag){
           case 0x00:{
                   printf("reserved\t");
                   break;
                 }
           case 0x01:{
                   printf("x1\t");
                   break;
                 }
           case 0x02:{
                   printf("x2\t");
                   break;
                 }
           case 0x04:{
                   printf("x4\t");
                   break;
                 }
           case 0x08:{
                   printf("x8\t");
                   break;
                 }
           case 0x0c:{
                   printf("x12\t");
                   break;
                 }
           case 0x10:{
                   printf("x16\t");
                   break;
                 }
           case 0x20:{
                   printf("x32\t");
                   break;
                 }
           default:{
                    printf("null\t");
                 }
  }
}

int main()
{  
    DWORD addr = 0;
    WORD bus,dev,fun;
    WORD* DATA;
    WORD* dwDATA;
    BYTE nextPoint;
    int fd,i = 1;
    fd = open("/dev/mem",O_RDWR);
    if(fd < 0)
    {
      printf("can't open port!\n");
    }
#ifdef DEBUG
    printf("fd = %d\n",fd);
#endif
     for(bus = 0; bus <= PCI_MAX_BUS; bus++)
        for(dev = 0; dev <= PCI_MAX_DEV; dev++)
           for(fun = 0; fun <= PCI_MAX_FUN; fun++)
              {
                 addr = 0;
                 addr = PCI_BASE_ADDR|(bus<<20)|(dev<<15)|(fun<<12);
                 DATA = mmap(NULL,LEN_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,addr);
                 if(DATA == (void *)-1)
                 {
                     munmap(DATA,LEN_SIZE);
                     break;
                 }
                 if(*DATA != 0xffffffff)
                 {
                    nextPoint = (BYTE)(*(DATA+0x34/4)); 
                    dwDATA = DATA+nextPoint/4;
                     #ifdef DEBUG1
                    printf("nextpoint = %x\n",nextPoint);
                    printf("addr1=%x\n",*dwDATA);
                     #endif                    
                 while(1){
                    if((BYTE)*(dwDATA)==0x10)
                     {        
                             
                             printf("PCI-E:");
                             printf("bus# = %x,dev# = %x,fun# = %x\t",bus,dev,fun);
                             printf("vender id:%.4x\t",*DATA&0xffff);
                             printf("device id:%.4x\t",((*DATA)>>8)&0XFFFF);
                             printf("\ntype:");
                             typeshow((BYTE)((*dwDATA)>>8)|0x0f);
                             speedshow(*(dwDATA+0x2c/4)>>1&0xf);
                          //   linkspeedshow(*(dwDATA+0x0c/4)&0xf);
                             linkwidthshow((*(dwDATA+0x0c/4)>>4)&0x3f);
                             printf("\n");
                             break;
                     }
                     dwDATA = DATA+((*(dwDATA)>>8)&0xff)/4;
                     #ifdef DEBUG1
                     printf("dwDATA = %x\n",*dwDATA);
                     #endif
    
                     if((BYTE)(*(dwDATA)) == 0x00)
                     break;
                     }
                 #ifdef DEBUG                    
                    printf("bus = %x,dev = %x,fun = %x\n",bus,dev,fun); 
                    for(i = 0; i < 0;i++)
                    {
                        printf("data%d:%x\n",i,*(DATA+i));
                    }
                   printf(" next Point:%x\n",nextPoint);
                   printf("data:%x\n",(BYTE)*(DATA+nextPoint/4));
                 #endif
                    
                 
                 }
                  munmap(DATA,LEN_SIZE);
              } 
    close(fd);
    return 0;
}
相關文章
相關標籤/搜索