PCIE的mmio內存映射訪問機制+ 配置空間 +mmap + resource + /dev/mem


https://blog.csdn.net/Jmilk/article/details/106007926

 

打開 dpdk-18.08/drivers/bus/pci/linux/pci.c 可以看到以下內容:

#define PCI_MAX_RESOURCE 6 /* * PCI 掃描文件系統下的 resource 文件 * @param filename: 通常為 /sys/bus/pci/devices/{pci_addr}/resource 文件 * @param dev[out]: dpdk 中對一個 PCI 設備的抽象 */ static int pci_parse_sysfs_resource(const char *filename, struct rte_pci_device *dev) { FILE *f; char buf[BUFSIZ]; int i; uint64_t phys_addr, end_addr, flags; f = fopen(filename, "r"); // 先打開 resource 文件,只讀 if (f == NULL) { RTE_LOG(ERR, EAL, "Cannot open sysfs resource\n"); return -1; } // 掃描 6 次,因為 PCI 最多有 6 個 BAR for (i = 0; i<PCI_MAX_RESOURCE; i++) { if (fgets(buf, sizeof(buf), f) == NULL) { RTE_LOG(ERR, EAL, "%s(): cannot read resource\n", __func__); goto error; } // 掃描 resource 文件拿到 BAR if (pci_parse_one_sysfs_resource(buf, sizeof(buf), &phys_addr, &end_addr, &flags) < 0) goto error; // 如果是 Memory BAR,則進行記錄 if (flags & IORESOURCE_MEM) { dev->mem_resource[i].phys_addr = phys_addr; dev->mem_resource[i].len = end_addr - phys_addr + 1; /* not mapped for now */ dev->mem_resource[i].addr = NULL; } } fclose(f); return 0; error: fclose(f); return -1; } /* * 掃描 PCI resource 文件中的某一行 * @param line: 某一行 * @param len: 長度,為第一個參數字符串的長度 * @param phys_addr[out]: PCI BAR 的起始地址,這個地址要 mmap() 才能用 * @param end_addr[out]: PCI BAR 的結束地址 * @param flags[out]: PCI BAR 的標志 */ int pci_parse_one_sysfs_resource(char *line, size_t len, uint64_t *phys_addr, uint64_t *end_addr, uint64_t *flags) { union pci_resource_info { struct { char *phys_addr; char *end_addr; char *flags; }; char *ptrs[PCI_RESOURCE_FMT_NVAL]; } res_info; // 字符串處理 if (rte_strsplit(line, len, res_info.ptrs, 3, ' ') != 3) { RTE_LOG(ERR, EAL, "%s(): bad resource format\n", __func__); return -1; } errno = 0; // 字符串處理,拿到 PCI BAR 起始地址、PCI BAR 結束地址、PCI BAR 標志 *phys_addr = strtoull(res_info.phys_addr, NULL, 16); *end_addr = strtoull(res_info.end_addr, NULL, 16); *flags = strtoull(res_info.flags, NULL, 16); if (errno != 0) { RTE_LOG(ERR, EAL, "%s(): bad resource format\n", __func__); return -1; } return 0; } 

 

這段代碼的邏輯很簡單,就是掃描某個 PCI 設備的 resource 文件並獲得 Memory BAR。e.g.

$ cat /sys/bus/pci/devices/0000:00:08.0/resource
0x0000000000001000 0x000000000000103f 0x0000000000040101 0x00000000c0040000 0x00000000c0040fff 0x0000000000040200 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000440000000 0x0000000440003fff 0x000000000014220c 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000c0000000 0x00000000c003ffff 0x000000000004e200
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000

 

前 6 行為 PCI 設備的 6 個 BAR,還是以 Intel 82599 為例,前兩個 BAR 為 Memory BAR,中間兩個 BAR 為 IO BAR,最后兩個 BAR 為 MSI-X BAR。其中,每個 BAR 又分為 3 列:

  1. 第 1 列為 PCI BAR 的起始地址
  2. 第 2 列為 PCI BAR 的終止地址
  3. 第 3 列為 PCI BAR 的標識

再看一段代碼 dpdk-18.08/drivers/bus/pci/linux/pci_uio.c:

/* * 用於映射 resource 資源,並獲取 PCI BAR * @param dev:DPDK 中關於某一個 PCI 設備的抽象實例 * @param res_id:說明要獲取第幾個 BAR * @param uio_res:用來存放 PCI BAR 資源的結構 * @param map_idx、uio_res:數組的計數器 */ int pci_uio_map_resource_by_index(struct rte_pci_device *dev, int res_idx, struct mapped_pci_resource *uio_res, int map_idx) { ..... // 省略 // 打開 /dev/bus/pci/devices/{pci_addr}/resource0..N 文件 if (!wc_activate || fd < 0) { snprintf(devname, sizeof(devname), "%s/" PCI_PRI_FMT "/resource%d", rte_pci_get_sysfs_path(), loc->domain, loc->bus, loc->devid, loc->function, res_idx); /* then try to map resource file */ fd = open(devname, O_RDWR); if (fd < 0) { RTE_LOG(ERR, EAL, "Cannot open %s: %s\n", devname, strerror(errno)); goto error; } } /* try mapping somewhere close to the end of hugepages */ if (pci_map_addr == NULL) pci_map_addr = pci_find_max_end_va(); // 進行 mmap() 映射,拿到 PCI BAR 在進程虛擬空間下的地址 mapaddr = pci_map_resource(pci_map_addr, fd, 0, (size_t)dev->mem_resource[res_idx].len, 0); close(fd); if (mapaddr == MAP_FAILED) goto error; pci_map_addr = RTE_PTR_ADD(mapaddr, (size_t)dev->mem_resource[res_idx].len); // 將拿到的 PCI BAR 映射至進程虛擬空間內的地址存起來 maps[map_idx].phaddr = dev->mem_resource[res_idx].phys_addr; maps[map_idx].size = dev->mem_resource[res_idx].len; maps[map_idx].addr = mapaddr; maps[map_idx].offset = 0; strcpy(maps[map_idx].path, devname); dev->mem_resource[res_idx].addr = mapaddr; return 0; error: rte_free(maps[map_idx].path); return -1; } /* * 對 pci/resource0..N 進行 mmap(),將 PCI BAR 空間通過 mmap 的方式映射到進程內部的虛擬空間,供用戶態應用來操作設備 */ void * pci_map_resource(void *requested_addr, int fd, off_t offset, size_t size, int additional_flags) { void *mapaddr; // 核心便是這句 mmap,其中要注意的是 offset 必須為 0 mapaddr = mmap(requested_addr, size, PROT_READ | PROT_WRITE, MAP_SHARED | additional_flags, fd, offset); if (mapaddr == MAP_FAILED) { RTE_LOG(ERR, EAL, "%s(): cannot mmap(%d, %p, 0x%zx, 0x%llx): %s (%p)\n", __func__, fd, requested_addr, size, (unsigned long long)offset, strerror(errno), mapaddr); } else RTE_LOG(DEBUG, EAL, " PCI memory mapped at %p\n", mapaddr); return mapaddr; }

https://github.com/billfarrow/pcimem


PCIe概述

PCI總線使用並行總線結構,采用單端並行信號,同一條總線上的所有設備共享總線帶寬 
PCIe總線使用高速差分總線,采用端到端連接方式,每一條PCIE鏈路只能連接兩個設備

PCIe的端到端連接方式 
發送端和接收端都含有TX(發送邏輯),RX(接受邏輯) 

 

 

現在來說明什么是mmio 

 mmio,memory map io內存映射訪問機制,除了port I/O之外,另外一種訪問方式就是mmio了 
內存映射,簡而言之就是將用戶空間的一段內存區域映射到內核空間,映射成功后,用戶對這段內存區域的修改可以直接反映到內核空間,同樣,內核空間對這段區域的修改也直接反映用戶空間。那么對於內核空間<—->用戶空間兩者之間需要大量數據傳輸等操作的話效率是非常高的。

 

這里就自然地提到一個函數,

mmap(linux環境) 

頭文件 <sys/mman.h>

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_toffset); 

int munmap(void* start,size_t length); 

 

mmap函數用來將內存空間映射到內核空間

munmap用來解除這個映射關系

 

函數參數:start映射區開始地址,設置為NULL時表示由系統決定

length映射區長度單位,地址自然是sizeof(unsigned long) 

prot設置為PORT_READ|PORT_WRITE表示頁可以被讀寫

flag指定映射對象類型,MAP_SHARED表示與其他所有映射這個對象的所有進程共享映射空間

fd,/dev/mem文件描述符

offset被映射對象內容的起點

 

這里還需要提到一個文件 /dev/mem 

“/dev/mem”物理內存全映像,可以用來訪問物理內存,一般是open("/dev/mem",O_RD_WR),然后mmap,接著就可以用mmap地址訪問物理內存(root權限)

 

這是PCIe物理內存地址!

遍歷時只需要根據相應的offset偏移即可PCIe設備的配置空間!

關於PCIe設備配置空間的0x34位置的Capabilites Pointer,需要說一說

這是0x34h位置即Capability Pointer所代表的空間,也就是說Capability Pointer雖然聽起來像一個指針,但是他只是一個8bit的數據,這里面存放了一個地址,而這個地址,就是我們要找的,

意義上Capability Pointer所指向的東西,我們來看看他究竟要指向哪里,就是這里嘍!

capbility pointer里面存放的地址就是上圖00h的地址嘍,也就是上表的基地址,那么問題來了我們為什么要找這個表呢?在這個表中,00h位置0~7bit是capbility ID,如果他的值是10h,就表明這是個PCIe設備的cap structure,也就是說我們已經找到了PCIe設備啦,如果他不是10h怎么辦?沒關系,因為在00h的7~15bit的地方還有next Cap pointer,跟Capbility structure原理類似,他也是個看起來像指針的字節,里面存放的就是下一個capbility structure的地址了,我們只需要根據這里的地址把基地址偏移過去就好了,直到我們找到next cap pointer為0的時候,就代表他后面已經沒有了,這時候我們就要跳出當前循環了

 

 

下面說明一下我們需要找到並打印出來的東西(Capbility structure表)

1.Type:02h的4~7bit,根據這個位置,將基地址偏移即可!

 

 

2.Speed:0x2ch的1~7bit,注意是1~7而不是0~7 

 

 

3.Link Speed:0x0ch的0~3bit 

 

 

4.Link Width:0x0ch的4~9bit,寄存器圖同上0x0ch

 

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h> //mmap
#include<fcntl.h>   //open filel
 
#define MAX_BUS 5  //一般來說到4就夠了
#define MAX_DEV 32
#define MAX_FUN 8
#define BASE_ADDR 0xf8000000  //我的電腦的基地址,你的可能不一樣哦
#define LEN_SIZE sizeof(unsigned long)
 
typedef unsigned int WORD;
typedef unsigned char BYTE;//8bit
 
 
void typeshow(BYTE data) //
{ 
    printf("%x\n",data);
    switch(data)
    {
        case 0x00:printf("PCIExpress Endpoint device\n");
                  break;
        case 0x01:printf("LegacyPCI Express Endpoint device\n");
                  break;
        case 0x04:printf("RootPort of PCI Express Root Complex\n");
                  break;
        case 0x05:printf("Upstream Port of PCI Express Switch\n");
                  break;
        case 0x06:printf("DownstreamPort of PCI Express Switch\n");
                  break;
        case 0x07:printf("PCiExpress-to-PCI/PCI-x Bridge\n");
                  break;
        case 0x08:printf("PCI/PCI-xto PCi Express Bridge\n");
                  break;
        case 0x09:printf("RootComplex Integrated Endpoint Device\n");
                  break;
        case 0x0a:printf("RootComplex Event Collector\n");
                  break;
 
        default:printf("reserved\n");
                break;
    }
    printf("\n");
}
 
void speedshow(BYTE speed)
{
    printf("%x\n",speed);
    switch(speed)
    {
        case 0x00:printf("2.5GT/S");
                  break;
        case 0x02:printf("5GT/S");
                  break;
        case 0x04:printf("8GT/S");
                  break;
 
        default:printf("reserved");
                break;
    }
    printf("\n\n");
}
 
void linkspeedshow(BYTE speed)
{
    printf("%x\n",speed);
    switch(speed)
    {
        case 0x01:printf("SupportedLink Speeds Vector filed bit 0");
                         break;
        case 0x02:printf("SupportedLink Speeds Vector filed bit 1");
                         break;
        case 0x03:printf("SupportedLink Speeds Vector filed bit 2");
                         break;
        case 0x04:printf("SupportedLink Speeds Vector filed bit 3");
                         break;
        case 0x05:printf("SupportedLink Speeds Vector filed bit 4");
                         break;
        case 0x06:printf("SupportedLink Speeds Vector filed bit 5");
                         break;
        case 0x07:printf("SupportedLink Speeds Vector filed bit 6");
                          break;
 
        default:printf("reserved");
                         break;
    }
    printf("\n\n");
}
 
void linkwidthshow(BYTE width)
{
    printf("%x\n",width);
    switch(width)
    {
        case 0x01:printf("x1");
                          break;
        case 0x02:printf("x2");
                         break;
        case 0x04:printf("x4");
                         break;
        case 0x08:printf("x8");
                         break;
        case 0x0c:printf("x12");
                          break;
        case 0x10:printf("x16");
                         break;
        case 0x20:printf("x32");
                         break;
 
        default:printf("reserved");
                         break;
    }
    printf("\n\n");
}
 
int main()
{
    WORD addr=0;
    WORDbus,dev,fun;
    WORD *ptrdata;
    WORD*ptrsearch;
    BYTE nextpoint;//8bit
 
    int fd;
    int i;
 
    fd=open("/dev/mem",O_RDWR);
    //“/dev/mem”物理內存全映像,可以用來訪問物理內存,一般是open("/dev/mem",O_RD_WR),然後mmap,接著就可以用mmap地址訪問物理內存
 
    if(fd<0)
    {
        printf("openmemory failed!\n");
        return -1;
    }
    printf("fd=%d\n",fd);
 
    for(bus=0;bus<MAX_BUS;++bus)
    {
        for(dev=0;dev<MAX_DEV;++dev)
        {
            for(fun=0;fun<MAX_FUN;++fun)
            {
                //addr=0;
               addr=BASE_ADDR|(bus<<20)|(dev<<15)|(fun<<12);//要尋找的偏移地址,根據PCIe的物理內存偏移
               ptrdata=mmap(NULL,LEN_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,addr);//映射后返回的首地址 ---物理地址 //void*mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
                //start映射區開始地址,設置為NULL時表示由系統決定
                //length映射區長度單位,地址自然是sizeof(unsigned long)
                //prot設置為PORT_READ|PORT_WRITE表示頁可以被讀寫
                //flag指定映射對象類型,MAP_SHARED表示與其他所有映射這個對象的所有進程共享映射空間
                //fd文件描述符
                //offset被映射對象內容的起點
 
                if(ptrdata==(void *)-1)
                {
                   munmap(ptrdata,LEN_SIZE);
                   break;
                }
                if(*ptrdata != 0xffffffff)
                {
                   nextpoint=(BYTE)(*(ptrdata+0x34/4));//capability pointer
                   ptrsearch=ptrdata + nextpoint/4;//the base address of capability structure
 
                   printf("next point:%x\n",nextpoint);
                   printf("ptrdata:%x\n",*ptrdata);
                   printf("search:%x\n",*ptrsearch);
 
                   while(1)//search for the pcie device
                   {
                       if((BYTE)(*ptrsearch)==0x10)//capability id of 10h indicating pcie capabilitystructure
                       {
                            printf("PCIE:");
                            printf("busnumber=%x,dev number=%x,function number=%x\n",bus,dev,fun);
                           printf("vender id:%x\t",(*ptrdata)&0x0000ffff);
                            printf("deviceid:%x\n",((*ptrdata)>>8)&0x0000ffff);
 
                            printf("ptrsearch:%x\n",*ptrsearch);
                            printf("type:");
                            typeshow((BYTE)(((*ptrsearch)>>20)&0x0f));
 
                            printf("speed:");
                           speedshow((BYTE)(((*(ptrsearch+0x2c/4))>>1)&0x7f));
 
                            printf("linkspeed:");
                            linkspeedshow((BYTE)(*(ptrsearch+0x0c/4)&0x0f));
 
                            printf("linkwidth:");
                           linkwidthshow((BYTE)(((*(ptrsearch+0x0c/4))>>4)&0x3f));
 
                            printf("***************************");
 
                            break;//havefound the pcie device and printed all the message,break the while
                       }
 
                       if((BYTE)((*ptrsearch)>>8)==0x00)//no pcie device exsist
                            break;
                       ptrsearch=ptrdata+((BYTE)(((*ptrsearch)>>8)&0x00ff))/4;//next cap pointer
                       printf("next pointer:%x\n",*ptrsearch);
                   }
                }
               munmap(ptrdata,LEN_SIZE);
            }
        }
    }
    close(fd);
    return 0;
}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM