1. 程式人生 > 實用技巧 >PCIE的mmio記憶體對映訪問機制+ 配置空間

PCIE的mmio記憶體對映訪問機制+ 配置空間

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

這段程式碼的邏輯很簡單,就是掃描某個 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;
}


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;
}